-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·600 lines (546 loc) · 24 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·600 lines (546 loc) · 24 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
#!/usr/bin/env bash
################################################################################
# GrokHunter Rootless — Termux full lab + desktop overlay-only installer
# Coding lab: Kali NetHunter (proot) on Termux; overlay coding tools on desktop
#
# https://github.com/FineComputer14451/GrokHunter
#
# BUILT ON (please support upstream — see CREDITS.md):
# jorexdeveloper — termux-nethunter & termux-distro (GPL-3.0)
# https://github.com/jorexdeveloper/termux-nethunter
# https://github.com/jorexdeveloper/termux-distro
# Termux team — host platform, packages, Termux:X11
# https://termux.dev · https://github.com/termux
# Kali Linux / Offensive Security — NetHunter rootfs images
# https://www.kali.org · https://kali.download/nethunter-images/
# xAI — Grok Build CLI & Grok models
# https://x.ai/cli · https://docs.x.ai
# Not affiliated with the above; we build upon their work with attribution.
################################################################################
set -euo pipefail
die() { echo "[GrokHunter] ERROR: $*" >&2; exit 1; }
warn() { echo "[GrokHunter] WARN: $*" >&2; }
# Status on stderr so command substitutions (e.g. resolve_distro_engine) stay clean.
info() { echo "[GrokHunter] $*" >&2; }
# Rich error helper with recovery steps
die_with_help() {
local msg="$1"
shift
echo "[GrokHunter] ERROR: ${msg}" >&2
if [[ $# -gt 0 ]]; then
echo >&2
echo "[GrokHunter] What to try:" >&2
local i=1
for step in "$@"; do
printf " %d. %s\n" "$i" "$step" >&2
((i++)) || true
done
fi
exit 1
}
REPO_RAW="https://raw.githubusercontent.com/FineComputer14451/GrokHunter/main"
REPO_TAR="https://github.com/FineComputer14451/GrokHunter/archive/refs/heads/main.tar.gz"
MODULES=(cli.sh actions.sh grok.sh x11.sh install-tui.sh)
DISCOVER_MODULES=(skills-discover.sh agents-discover.sh personas-discover.sh roles-discover.sh)
MODULES_VERSION="2026.8.27"
OVERLAY_ITEMS=(
install.sh uninstall.sh VERSION LICENSE CREDITS.md AGENTS.md README.md CHANGELOG.md
)
OVERLAY_DIRS=(
bin lib scripts skills agents personas roles config templates branding docs
)
CLEANUP_TMP=""
WAKE_HELD=0
cleanup() {
local ec=$?
[[ -n "${CLEANUP_TMP:-}" && -d "${CLEANUP_TMP}" ]] && rm -rf "${CLEANUP_TMP}" || true
if [[ "${WAKE_HELD}" -eq 1 ]] && command -v termux-wake-unlock >/dev/null 2>&1; then
termux-wake-unlock 2>/dev/null || true
fi
exit "${ec}"
}
trap cleanup EXIT
# Host: termux (Android full lab) vs desktop (Linux/macOS/WSL overlay-only).
# Override for tests: GROKHUNTER_HOST=termux|desktop
_gh_detect_host() {
local forced="${GROKHUNTER_HOST:-}"
if [[ -n "${forced}" ]]; then
case "${forced}" in
termux|desktop) return 0 ;;
*) warn "Unknown GROKHUNTER_HOST=${forced}; auto-detecting" ;;
esac
fi
if [[ -n "${PREFIX:-}" && "${PREFIX}" == *com.termux* ]]; then
GROKHUNTER_HOST=termux
return 0
fi
if [[ -d /data/data/com.termux/files/usr ]]; then
GROKHUNTER_HOST=termux
return 0
fi
GROKHUNTER_HOST=desktop
}
_gh_detect_host
export GROKHUNTER_HOST
if [[ "${GROKHUNTER_HOST}" == "termux" ]]; then
if [[ -z "${PREFIX:-}" || "${PREFIX}" != *com.termux* ]]; then
export PREFIX="${PREFIX:-/data/data/com.termux/files/usr}"
fi
export PATH="${PREFIX}/bin:${PATH:-}"
export HOME="${HOME:-/data/data/com.termux/files/home}"
export TMPDIR="${TMPDIR:-${PREFIX}/tmp}"
else
# Desktop: keep real HOME / TMPDIR / PATH. Do not force Android PREFIX.
if [[ -n "${PREFIX:-}" && "${PREFIX}" == *com.termux* ]]; then
unset PREFIX
fi
: "${HOME:=${HOME:-}}"
if [[ -z "${HOME}" ]]; then
HOME="$(getent passwd "$(id -un)" 2>/dev/null | cut -d: -f6 || true)"
fi
: "${HOME:=${PWD:-/tmp}}"
export HOME
if [[ -z "${TMPDIR:-}" || ! -d "${TMPDIR}" ]]; then
export TMPDIR="${TMPDIR:-/tmp}"
fi
info "Desktop host detected (GROKHUNTER_HOST=desktop) — overlay-only coding tools (no NetHunter rootfs)"
fi
if ! mkdir -p "${TMPDIR}" 2>/dev/null; then
if [[ "${GROKHUNTER_HOST}" == "termux" ]]; then
die_with_help "Cannot create TMPDIR=${TMPDIR}." \
"Check that Termux has storage permission (run: termux-setup-storage)" \
"Restart Termux and try again."
fi
die_with_help "Cannot create TMPDIR=${TMPDIR}." \
"Set TMPDIR to a writable directory (e.g. export TMPDIR=/tmp)" \
"Then re-run the installer."
fi
if [[ ! -w "${HOME}" ]]; then
if [[ "${GROKHUNTER_HOST}" == "termux" ]]; then
die_with_help "HOME is not writable: ${HOME}" \
"Run: termux-setup-storage" \
"Then restart Termux and re-run the installer."
fi
die_with_help "HOME is not writable: ${HOME}" \
"Export HOME to a writable directory and re-run."
fi
_missing=()
for c in curl tar bash; do command -v "$c" >/dev/null 2>&1 || _missing+=("$c"); done
if [[ "${#_missing[@]}" -gt 0 ]]; then
if [[ "${GROKHUNTER_HOST}" == "termux" ]]; then
info "Installing Termux prerequisites (curl tar)..."
command -v pkg >/dev/null 2>&1 || die_with_help "pkg command not found." \
"You must be running inside a working Termux environment." \
"Reinstall Termux from F-Droid or GitHub if needed."
pkg update -y >/dev/null 2>&1 || warn "pkg update failed (continuing)"
pkg install -y curl tar >/dev/null 2>&1 || die_with_help "Failed to install curl and tar." \
"Run manually: pkg update && pkg install -y curl tar" \
"Then re-run this installer."
else
die_with_help "Missing required tools on desktop: ${_missing[*]}" \
"Debian/Ubuntu/WSL: sudo apt update && sudo apt install -y curl tar bash" \
"Fedora/RHEL: sudo dnf install -y curl tar bash" \
"macOS (Homebrew): brew install curl" \
"Then re-run this installer."
fi
fi
command -v curl >/dev/null 2>&1 || die_with_help "curl is still missing after install attempt." \
"Termux: pkg install -y curl" \
"Desktop: install curl via apt/dnf/brew, then re-run."
command -v tar >/dev/null 2>&1 || die_with_help "tar is still missing after install attempt." \
"Termux: pkg install -y tar" \
"Desktop: install tar via apt/dnf (usually preinstalled), then re-run."
# Termux-only wake lock (no-op on desktop).
if [[ "${GROKHUNTER_HOST}" == "termux" ]] && command -v termux-wake-lock >/dev/null 2>&1; then
if termux-wake-lock 2>/dev/null; then WAKE_HELD=1; else warn "termux-wake-lock failed"; fi
fi
if [[ -n "${BASH_SOURCE[0]:-}" && -f "${BASH_SOURCE[0]}" ]]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || SCRIPT_DIR=""
else SCRIPT_DIR=""; fi
# Process substitution (`bash <(curl …)`) yields /dev/fd or /proc/self/fd — not a real overlay.
_gh_is_ephemeral_dir() {
local d="${1:-}"
[[ -z "$d" ]] && return 0
case "$d" in
/dev/fd|/dev/fd/*|/proc/self/fd|/proc/self/fd/*|/proc/*/fd|/proc/*/fd/*) return 0 ;;
esac
[[ "$d" == /dev/fd* || "$d" == /proc/*/fd* ]] && return 0
return 1
}
if _gh_is_ephemeral_dir "${SCRIPT_DIR}" || [[ ! -f "${SCRIPT_DIR}/install.sh" ]]; then
SCRIPT_DIR=""
fi
LIB_DIR=""
OVERLAY_ROOT=""
CACHE_DIR="${HOME}/.cache/grokhunter"
REFRESH="${GROKHUNTER_REFRESH:-0}"
validate_module() {
local f="$1"
[[ -f "$f" && -s "$f" ]] || return 1
grep -qE '^[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)' "$f" 2>/dev/null || return 1
return 0
}
_gh_overlay_complete() {
local dir="${1:-}"
[[ -n "${dir}" && -d "${dir}" ]] || return 1
[[ -f "${dir}/install.sh" ]] || return 1
[[ -f "${dir}/lib/grok.sh" ]] || return 1
[[ -f "${dir}/lib/skills-discover.sh" ]] || return 1
[[ -f "${dir}/lib/agents-discover.sh" ]] || return 1
[[ -f "${dir}/lib/personas-discover.sh" ]] || return 1
[[ -f "${dir}/lib/roles-discover.sh" ]] || return 1
[[ -f "${dir}/bin/grokhunter" ]] || return 1
[[ -f "${dir}/scripts/install-completions.sh" ]] || return 1
[[ -f "${dir}/scripts/ensure_grok.sh" ]] || return 1
[[ -d "${dir}/skills" ]] || return 1
return 0
}
# True if dir is a real GrokHunter overlay (complete tree or git clone), not a stray install.sh.
_gh_managed_overlay() {
local dir="${1:-}"
[[ -n "${dir}" && -d "${dir}" ]] || return 1
_gh_is_ephemeral_dir "${dir}" && return 1
if _gh_overlay_complete "${dir}"; then
return 0
fi
if [[ -f "${dir}/install.sh" && -d "${dir}/.git" ]]; then
return 0
fi
return 1
}
# Clone / GROKHUNTER_HOME / ~/GrokHunter / cache src (never a foreign non-empty dir).
_gh_pick_overlay_dest() {
local home_dest="${HOME}/GrokHunter"
local cache_src="${CACHE_DIR}/src"
if [[ -n "${SCRIPT_DIR}" ]] && _gh_managed_overlay "${SCRIPT_DIR}"; then
printf '%s\n' "${SCRIPT_DIR}"
return 0
fi
if [[ -n "${GROKHUNTER_HOME:-}" ]] && _gh_managed_overlay "${GROKHUNTER_HOME}"; then
printf '%s\n' "${GROKHUNTER_HOME}"
return 0
fi
if [[ ! -e "${home_dest}" ]]; then
printf '%s\n' "${home_dest}"
return 0
fi
if [[ -d "${home_dest}" ]]; then
if [[ -z "$(ls -A "${home_dest}" 2>/dev/null)" ]]; then
printf '%s\n' "${home_dest}"
return 0
fi
if _gh_managed_overlay "${home_dest}"; then
printf '%s\n' "${home_dest}"
return 0
fi
fi
printf '%s\n' "${cache_src}"
}
_gh_stamp_overlay() {
mkdir -p "${CACHE_DIR}" || true
printf '%s\n' "${MODULES_VERSION}" > "${CACHE_DIR}/MODULES_VERSION"
printf '%s\n' "${OVERLAY_ROOT}" > "${CACHE_DIR}/OVERLAY_ROOT"
if [[ -n "${LIB_DIR}" && -d "${LIB_DIR}" ]]; then
rm -rf "${CACHE_DIR}/lib"
cp -a "${LIB_DIR}" "${CACHE_DIR}/" 2>/dev/null || true
fi
}
_gh_fetch_repo_tarball() {
CLEANUP_TMP="$(mktemp -d "${TMPDIR}/grokhunter.XXXXXX")" || die "mktemp failed"
info "Fetching GrokHunter overlay (tarball)…"
if curl -fsSL --connect-timeout 20 --max-time 180 --retry 2 \
"${REPO_TAR}" | tar -xz -C "${CLEANUP_TMP}" --strip-components=1 2>/dev/null \
&& [[ -d "${CLEANUP_TMP}/lib" && -f "${CLEANUP_TMP}/install.sh" ]]; then
return 0
fi
die_with_help "Failed to download GrokHunter overlay from GitHub." \
"Check your internet connection" \
"Force a clean re-download: GROKHUNTER_REFRESH=1 bash <(curl -fsSL ${REPO_RAW}/install.sh) --overlay-only --with-completions" \
"Or clone and run locally: git clone https://github.com/FineComputer14451/GrokHunter.git && cd GrokHunter && bash install.sh"
}
_gh_install_overlay_from_tmp() {
local dest="$1"
local item
[[ -n "${CLEANUP_TMP}" && -d "${CLEANUP_TMP}" ]] || die "overlay tmp missing"
mkdir -p "${dest}" || die "cannot create overlay dest ${dest}"
for item in "${OVERLAY_ITEMS[@]}"; do
if [[ -e "${CLEANUP_TMP}/${item}" ]]; then
cp -a "${CLEANUP_TMP}/${item}" "${dest}/" || die "failed to copy ${item}"
fi
done
for item in "${OVERLAY_DIRS[@]}"; do
if [[ -d "${CLEANUP_TMP}/${item}" ]]; then
rm -rf "${dest}/${item}"
cp -a "${CLEANUP_TMP}/${item}" "${dest}/" || die "failed to copy ${item}/"
fi
done
}
# One-liner: extract full overlay (bin/scripts/skills). Clone: local tree always wins.
ensure_overlay_tree() {
mkdir -p "${CACHE_DIR}" || die "cannot create cache ${CACHE_DIR}"
local dest
dest="$(_gh_pick_overlay_dest)"
# Git clone: local tree always wins (including REFRESH=1). Never tar onto .git.
if [[ -n "${SCRIPT_DIR}" && "${dest}" == "${SCRIPT_DIR}" && -d "${SCRIPT_DIR}/.git" ]]; then
if ! _gh_overlay_complete "${SCRIPT_DIR}"; then
die_with_help "Git clone overlay is incomplete (missing bin/scripts/skills)." \
"Run: git pull" \
"Or clone a fresh copy: git clone https://github.com/FineComputer14451/GrokHunter.git"
fi
OVERLAY_ROOT="${SCRIPT_DIR}"
LIB_DIR="${SCRIPT_DIR}/lib"
export GROKHUNTER_HOME="${OVERLAY_ROOT}"
info "Using local overlay: ${OVERLAY_ROOT}"
_gh_stamp_overlay
return 0
fi
# Extracted (non-git) tree: use local when complete unless REFRESH=1.
# Incomplete trees fall through so the tarball can repair them.
if [[ -n "${SCRIPT_DIR}" && "${dest}" == "${SCRIPT_DIR}" && -d "${SCRIPT_DIR}/lib" ]] \
&& [[ "${REFRESH}" != "1" ]] && _gh_overlay_complete "${SCRIPT_DIR}"; then
OVERLAY_ROOT="${SCRIPT_DIR}"
LIB_DIR="${SCRIPT_DIR}/lib"
export GROKHUNTER_HOME="${OVERLAY_ROOT}"
info "Using local overlay: ${OVERLAY_ROOT}"
_gh_stamp_overlay
return 0
fi
if [[ "${REFRESH}" != "1" ]] && _gh_overlay_complete "${dest}" \
&& [[ "$(cat "${CACHE_DIR}/MODULES_VERSION" 2>/dev/null || true)" == "${MODULES_VERSION}" ]]; then
OVERLAY_ROOT="${dest}"
LIB_DIR="${dest}/lib"
SCRIPT_DIR="${dest}"
export GROKHUNTER_HOME="${dest}"
info "Cache hit → overlay ${OVERLAY_ROOT} (v${MODULES_VERSION})"
_gh_stamp_overlay
return 0
fi
if [[ -d "${dest}/.git" ]]; then
die_with_help "Refusing to extract tarball onto git clone: ${dest}" \
"cd ${dest} && git pull" \
"Or: GROKHUNTER_HOME=${HOME}/GrokHunter bash install.sh --overlay-only --with-completions"
fi
[[ "${REFRESH}" == "1" ]] && info "Refreshing overlay from GitHub tarball..."
if [[ "${GROKHUNTER_HOST:-termux}" == "desktop" ]]; then
info "Desktop bootstrap (full overlay)…"
else
info "Termux one-liner bootstrap (full overlay)…"
fi
_gh_fetch_repo_tarball
_gh_install_overlay_from_tmp "${dest}"
_gh_overlay_complete "${dest}" || die_with_help "Extracted overlay looks incomplete: ${dest}" \
"Force a clean re-download: GROKHUNTER_REFRESH=1 bash <(curl -fsSL ${REPO_RAW}/install.sh) --overlay-only --with-completions" \
"Or clone: git clone https://github.com/FineComputer14451/GrokHunter.git && cd GrokHunter && bash install.sh"
OVERLAY_ROOT="${dest}"
LIB_DIR="${dest}/lib"
SCRIPT_DIR="${dest}"
export GROKHUNTER_HOME="${dest}"
_gh_stamp_overlay
info "Overlay ready → ${OVERLAY_ROOT}"
}
ensure_overlay_tree
[[ -n "${LIB_DIR}" && -d "${LIB_DIR}" ]] || die "LIB_DIR not set"
for m in "${MODULES[@]}" "${DISCOVER_MODULES[@]}"; do
validate_module "${LIB_DIR}/${m}" || die_with_help "Missing or invalid module: ${LIB_DIR}/${m}" \
"Force a clean re-download: GROKHUNTER_REFRESH=1 bash install.sh --overlay-only --with-completions" \
"Or clone a fresh copy of the repository and run bash install.sh"
done
# shellcheck source=/dev/null
source "${LIB_DIR}/cli.sh" || die "failed to source cli.sh"
# shellcheck source=/dev/null
source "${LIB_DIR}/install-tui.sh" || die "failed to source install-tui.sh"
# shellcheck source=/dev/null
source "${LIB_DIR}/actions.sh" || die "failed to source actions.sh"
# shellcheck source=/dev/null
source "${LIB_DIR}/grok.sh" || die "failed to source grok.sh"
# shellcheck source=/dev/null
source "${LIB_DIR}/x11.sh" || die "failed to source x11.sh"
for fn in parse_cli install_tui_main install_grok_build setup_termux_x11; do
declare -F "$fn" >/dev/null 2>&1 || die_with_help "Installer modules are incomplete or corrupted (missing function: $fn)." \
"Force a clean re-download: GROKHUNTER_REFRESH=1 bash install.sh" \
"Or clone a fresh copy of the repository"
done
DISTRO_NAME="Kali NetHunter"
PROGRAM_NAME="install.sh"
DISTRO_REPOSITORY=termux-nethunter
KERNEL_RELEASE=$(uname -r 2>/dev/null || echo unknown)
VERSION_NAME="GrokHunter-Rootless-2026.8.27"
SHASUM_CMD=sha256sum
# Offline / air-gapped fallbacks (live SHA from Kali /current/ is preferred).
# Last verified: 2026-08-05
TRUSTED_SHASUMS_FALLBACK_DATE="2026-08-05"
TRUSTED_SHASUMS="b8098fc90ed74a553592f7019a1d88dfe3c65b16c60af487b0658860554dc5aa kali-nethunter-rootfs-full-arm64.tar.xz
b15a4aba9fb1c6f7481d7b3d08cb77c9e9c993eb542475961d008bdc64767d64 kali-nethunter-rootfs-full-armhf.tar.xz
08f121b553d03476b82b6322365eb4f47f73f4edf8800dafa7462b061eb2d0fc kali-nethunter-rootfs-minimal-arm64.tar.xz
1ff5a8313cca728cf3c967bd2c8b59c629e8d4b9f4b35bf62b9df9f0097c8c1d kali-nethunter-rootfs-minimal-armhf.tar.xz
484af462afa5064512f420d8565a90c7923ac6288f35d37d37dff6aa44936a23 kali-nethunter-rootfs-nano-arm64.tar.xz
d0761b79c0b303401a1ac405db1b2b223b0e3e8d60ec647a6b391fd70c595fdf kali-nethunter-rootfs-nano-armhf.tar.xz"
ARCHIVE_STRIP_DIRS=1
BASE_URL=https://kali.download/nethunter-images/current/rootfs
TERMUX_FILES_DIR=/data/data/com.termux/files
DISTRO_SHORTCUT=${TERMUX_FILES_DIR}/usr/bin/nh
DISTRO_LAUNCHER=${TERMUX_FILES_DIR}/usr/bin/nethunter
DEFAULT_ROOTFS_DIR=${TERMUX_FILES_DIR}/kali
DEFAULT_LOGIN=kali
parse_cli "$@" || die_with_help "Invalid command-line options." "Run with --help to see available flags" "Example: bash install.sh --full --de xfce --with-grok --with-x11" "Desktop: bash install.sh --overlay-only --with-grok --with-completions"
# Desktop (Linux/macOS/WSL): always overlay-only. Full NetHunter rootfs is Termux-specific.
if [[ "${GROKHUNTER_HOST}" == "desktop" ]]; then
if [[ "${OVERLAY_ONLY}" -ne 1 ]]; then
info "Desktop host: forcing --overlay-only (Kali NetHunter rootfs is Termux/Android-only)"
OVERLAY_ONLY=1
NON_INTERACTIVE=1
fi
if [[ "${FEATURE_X11}" == "yes" ]]; then
warn "Termux:X11 is Android/Termux-only — skipping --with-x11 on desktop"
FEATURE_X11=no
INSTALL_X11=0
SKIP_X11=1
fi
fi
if [[ ${NON_INTERACTIVE} -eq 0 ]]; then
if [[ ! -t 0 || ! -t 1 ]]; then
if [[ "${GROKHUNTER_HOST}" == "desktop" ]]; then
die_with_help "Not a TTY. On desktop pass overlay flags." "bash install.sh --overlay-only --with-grok --with-completions" "Or: bash <(curl -fsSL ${REPO_RAW}/install.sh) --overlay-only --with-grok --with-completions"
fi
die_with_help "Not a TTY. Pass flags or run in a Termux terminal." "Coding-only: bash install.sh --nano --no-de --with-grok --with-completions" "Full lab: bash install.sh --full --de xfce --browser chromium --with-grok --with-x11 --with-completions" "Default: bash install.sh --yes"
fi
if install_tui_should_run; then
install_tui_main
fi
fi
# Lightweight messaging when termux-distro is not loaded (overlay-only).
_ensure_msg_stubs() {
if declare -F msg >/dev/null 2>&1; then
return 0
fi
msg() {
# Compatible with termux-distro msg flags: -t -tn -ts -tw -te -a
shift || true
printf '[GrokHunter] %s\n' "$*"
}
cursor() { :; }
}
# Overlay-only: optional packages without re-downloading NetHunter rootfs.
if [[ "${OVERLAY_ONLY:-0}" -eq 1 ]]; then
info "Overlay-only mode — skipping rootfs / termux-distro engine"
: "${ROOTFS_DIRECTORY:=${DEFAULT_ROOTFS_DIR:-/data/data/com.termux/files/kali}}"
: "${TERMUX_FILES_DIR:=/data/data/com.termux/files}"
: "${P:=}" "${S:=}" "${T:=}" "${W:=}" "${B:=}" "${M:=}"
_ensure_msg_stubs
if [[ -z "${GROKHUNTER_HOME:-}" ]] || _gh_is_ephemeral_dir "${GROKHUNTER_HOME}"; then
export GROKHUNTER_HOME="${OVERLAY_ROOT:-${SCRIPT_DIR:-${HOME}/GrokHunter}}"
fi
if _gh_is_ephemeral_dir "${GROKHUNTER_HOME}"; then
export GROKHUNTER_HOME="${HOME}/GrokHunter"
fi
# Default: if user passed only --overlay-only with no --with-*, nothing runs.
# Require at least one explicit yes feature, or warn.
if [[ "${FEATURE_GROK}" == "auto" && "${FEATURE_X11}" == "auto" && "${FEATURE_AIDER}" == "auto" && "${FEATURE_V9}" == "auto" && "${FEATURE_COMPLETIONS}" == "auto" ]]; then
if [[ "${GROKHUNTER_HOST}" == "desktop" ]]; then
die_with_help "Desktop overlay-only needs at least one --with-* flag." "Suggested: bash install.sh --overlay-only --with-grok --with-completions" "One-liner: bash <(curl -fsSL ${REPO_RAW}/install.sh) --overlay-only --with-grok --with-completions"
fi
die_with_help "Overlay-only needs at least one --with-* flag." "Example: bash install.sh --overlay-only --with-x11 --with-aider" "Example: bash install.sh --overlay-only --with-grok --with-completions"
fi
run_optional_features
echo
info "Overlay-only complete."
info " PATH wrappers: ~/.local/bin (grokhunter, doctor, …)"
info " doctor: grokhunter doctor # or bash ${GROKHUNTER_HOME}/bin/grokhunter-doctor"
info " models: grokhunter models status"
exit 0
fi
# termux-distro msg() uses ${P}/${S}/${N}/... before colors() runs.
# Under set -u that is fatal (\"P: unbound variable\"). Seed + disable nounset.
: "${N:=}" "${R:=}" "${G:=}" "${Y:=}" "${B:=}" "${M:=}" "${C:=}" "${W:=}"
: "${P:=}" "${S:=}" "${T:=}"
: "${HC:=}" "${RC:=}" "${SU:=}" "${RU:=}" "${SV:=}" "${RV:=}"
: "${ENABLE_COLOR:=1}"
: "${SYS_ARCH:=}" "${ROOTFS_DIRECTORY:=}" "${LOG_FILE:=/dev/null}"
: "${KEEP_ROOTFS_DIRECTORY:=}" "${DE_INSTALLED:=}"
_source_termux_distro() {
local engine="$1"
shift
set +u
# shellcheck source=/dev/null
source "${engine}" "$@"
local ec=$?
set -u
return "${ec}"
}
# Validate termux-distro engine: non-empty, not an HTML error page, looks like shell.
validate_distro_engine() {
local f="$1"
[[ -f "$f" && -s "$f" ]] || return 1
if head -c 256 "$f" 2>/dev/null | grep -qiE '<!DOCTYPE|<html'; then
return 1
fi
if head -1 "$f" 2>/dev/null | grep -qE '^#!'; then
return 0
fi
grep -qE '^[a-zA-Z_][a-zA-Z0-9_]*\s*\(\)' "$f" 2>/dev/null
}
# Prefer vendored → cached (same URL stamp) → download into ~/.cache (never pollute CWD).
# Override URL: GROKHUNTER_DISTRO_ENGINE_URL (honored even when cache exists)
resolve_distro_engine() {
local default_url="https://raw.githubusercontent.com/jorexdeveloper/termux-distro/main/termux-distro.sh"
local url="${GROKHUNTER_DISTRO_ENGINE_URL:-${default_url}}"
local cache_file="${CACHE_DIR}/termux-distro.sh"
local stamp_file="${CACHE_DIR}/termux-distro.url"
local dest=""
local stamp=""
if [[ -n "${SCRIPT_DIR}" && -f "${SCRIPT_DIR}/termux-distro.sh" ]]; then
if validate_distro_engine "${SCRIPT_DIR}/termux-distro.sh"; then
printf '%s\n' "${SCRIPT_DIR}/termux-distro.sh"
return 0
fi
warn "Vendored termux-distro.sh failed validation — trying cache/download"
fi
stamp="$(cat "${stamp_file}" 2>/dev/null || true)"
if [[ "${REFRESH}" != "1" ]] && validate_distro_engine "${cache_file}"; then
if [[ "${stamp}" == "${url}" ]]; then
info "Using cached termux-distro engine → ${cache_file}"
printf '%s\n' "${cache_file}"
return 0
fi
# Pre-stamp caches (no .url file) are treated as the default engine.
if [[ -z "${GROKHUNTER_DISTRO_ENGINE_URL:-}" && -z "${stamp}" ]]; then
info "Using cached termux-distro engine → ${cache_file}"
printf '%s\n' "${cache_file}"
return 0
fi
info "Engine URL stamp mismatch — fetching ${url}"
fi
mkdir -p "${CACHE_DIR}" || die "cannot create cache ${CACHE_DIR}"
dest="$(mktemp "${CACHE_DIR}/termux-distro.XXXXXX")" || die "mktemp failed for distro engine"
info "Fetching termux-distro engine…"
if ! curl -fsSL --connect-timeout 15 --max-time 90 --retry 2 \
"${url}" -o "${dest}"; then
rm -f "${dest}"
die_with_help "Could not download the termux-distro engine." \
"Check your internet connection / DNS" \
"Pin a fork: GROKHUNTER_DISTRO_ENGINE_URL=https://… bash install.sh" \
"Or vendor termux-distro.sh next to install.sh"
fi
if ! validate_distro_engine "${dest}"; then
rm -f "${dest}"
die_with_help "Downloaded termux-distro engine looks invalid (empty or HTML)." \
"Check GROKHUNTER_DISTRO_ENGINE_URL if set" \
"Force refresh: GROKHUNTER_REFRESH=1 bash install.sh" \
"Or clone the repo and vendor termux-distro.sh"
fi
mv -f "${dest}" "${cache_file}" || die "failed to install engine cache at ${cache_file}"
printf '%s\n' "${url}" > "${stamp_file}" || true
info "Engine cached at ${cache_file}"
printf '%s\n' "${cache_file}"
}
if [[ "${GROKHUNTER_HOST}" == "desktop" ]]; then
die_with_help "Full NetHunter rootfs install requires Termux on Android." "On Linux/macOS/WSL use overlay-only GrokHunter coding tools:" " bash install.sh --overlay-only --with-grok --with-completions" "Install Termux from F-Droid for the full lab: https://f-droid.org/packages/com.termux/"
fi
DISTRO_ENGINE="$(resolve_distro_engine)" || die_with_help "Could not resolve termux-distro engine." "GROKHUNTER_REFRESH=1 bash install.sh" "Or vendor termux-distro.sh next to install.sh"
# parse_cli already applied GrokHunter flags into SELECTED_* / FEATURE_* / etc.
# Do NOT forward "$@" into termux-distro — it only accepts its own options
# (-d, -l, --install-only, …) and rejects --full / --with-* as "Unrecognized option".
_source_termux_distro "${DISTRO_ENGINE}" || die_with_help "termux-distro engine failed." \
"Try again with a stable connection" \
"Force refresh: GROKHUNTER_REFRESH=1 bash install.sh" \
"Or clone the repo and run: bash install.sh"