-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·1489 lines (1338 loc) · 60.5 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·1489 lines (1338 loc) · 60.5 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
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# shellcheck disable=SC2154,SC2034
# Safer bash defaults
set -o pipefail
set -E
set +e
IFS=$'\n\t'
# Detect if the script is being run in macOS and re-exec with modern Bash.
# Supports both Apple Silicon (/opt/homebrew) and Intel (/usr/local) Homebrew prefixes.
if [[ $OSTYPE == "darwin"* ]]; then
_mac_bash=""
for _candidate in /opt/homebrew/bin/bash /usr/local/bin/bash /bin/bash; do
if [[ -x "$_candidate" ]]; then
_major="$("$_candidate" -lc 'echo "${BASH_VERSINFO[0]}"' 2>/dev/null || echo 0)"
if [[ "$_major" =~ ^[0-9]+$ ]] && [[ "$_major" -ge 4 ]]; then
_mac_bash="$_candidate"
break
fi
fi
done
if [[ -n "$_mac_bash" ]] && [[ "$BASH" != "$_mac_bash" ]]; then
exec "$_mac_bash" "$0" "$@"
fi
unset _mac_bash _candidate _major
fi
# Load main configuration
CONFIG_FILE="./reconftw.cfg"
if [[ ! -f $CONFIG_FILE ]]; then
printf "%b[!] Config file reconftw.cfg not found.%b\n" "$bred" "$reset"
exit 1
fi
# shellcheck source=./reconftw.cfg
if ! source "$CONFIG_FILE"; then
printf "[!] Failed to parse config file %s. Check for syntax errors.\n" "$CONFIG_FILE" >&2
exit 1
fi
# Initialize variables
dir="${tools}"
double_check=false
# ARM Detection
ARCH=$(uname -m)
# macOS Detection
IS_MAC=$([[ $OSTYPE == "darwin"* ]] && echo "True" || echo "False")
# timeout/gtimeout compatibility
if command -v timeout >/dev/null 2>&1; then
TIMEOUT_CMD="timeout"
elif command -v gtimeout >/dev/null 2>&1; then
TIMEOUT_CMD="gtimeout"
else
TIMEOUT_CMD=""
fi
# Globals for CLI overrides
FORCE_UPDATE=${FORCE_UPDATE:-false}
VERBOSE=${VERBOSE:-false}
LOGFILE=${LOGFILE:-"./install.log"}
DRY_RUN=${DRY_RUN:-false}
TOOLS_ONLY=${TOOLS_ONLY:-false}
# Log all output (default: install.log in repo root)
if [[ -n ${LOGFILE} ]]; then
: > "${LOGFILE}"
exec > >(tee -a "${LOGFILE}") 2>&1
fi
# Helper: run with timeout seconds if available
run_to() {
local secs=$1
shift || true
if [[ -n $TIMEOUT_CMD ]]; then "$TIMEOUT_CMD" "$secs" "$@"; else "$@"; fi
}
# verify_sha256 <file> <expected-hex>
# Returns 0 if the file's SHA-256 matches expected, 1 otherwise. Works with
# both GNU sha256sum (Linux) and shasum -a 256 (macOS).
verify_sha256() {
local file="$1"
local expected="$2"
local actual=""
[[ -n "$expected" ]] || return 0 # nothing to verify
[[ -s "$file" ]] || return 1
if command -v sha256sum >/dev/null 2>&1; then
actual=$(sha256sum "$file" | awk '{print $1}')
elif command -v shasum >/dev/null 2>&1; then
actual=$(shasum -a 256 "$file" | awk '{print $1}')
else
printf '[WARN] verify_sha256: no sha256sum or shasum found; skipping integrity check for %s\n' "$file" >&2
return 0 # preserve backward compat but warn loudly
fi
[[ -n "$actual" && "$actual" == "$expected" ]]
}
# Helper: optionally dry-run
run_cmd() {
if [[ $DRY_RUN == "true" ]]; then
printf "%s\n" "[DRY-RUN] $*"
return 0
fi
"$@"
}
# Helper: quiet run (respect VERBOSE)
q() {
if [[ $DRY_RUN == "true" ]]; then
printf "%s\n" "[DRY-RUN] $*"
return 0
fi
if [[ $VERBOSE == "true" ]]; then "$@"; else { "$@"; } &>/dev/null; fi
}
# Helper: quiet run with timeout
q_to() {
local secs=$1
shift || true
if [[ $DRY_RUN == "true" ]]; then
printf "%s\n" "[DRY-RUN] (to ${secs}) $*"
return 0
fi
if [[ -n $TIMEOUT_CMD ]]; then
if [[ $VERBOSE == "true" ]]; then "$TIMEOUT_CMD" "$secs" "$@"; else { "$TIMEOUT_CMD" "$secs" "$@"; } &>/dev/null; fi
else
if [[ $VERBOSE == "true" ]]; then "$@"; else { "$@"; } &>/dev/null; fi
fi
}
# Helper: retry with linear backoff
retry() {
local attempts=$1
local delay=$2
shift 2
local n=0
until "$@"; do
n=$((n + 1))
if ((n >= attempts)); then return 1; fi
sleep $((delay * n))
done
}
ensure_git_dir() {
local _path="$1"
if [[ -d "$_path" && ! -d "$_path/.git" ]]; then
rm -rf "$_path" 2>/dev/null || true
fi
}
# Install Rust toolchain, uv package manager, smugglex, and shodan CLI.
# Called from install_apt/install_yum/install_pacman/install_brew to avoid duplication.
install_rust_uv() {
local _tmpfile
local _expected
# Install rustup via downloaded script (verify before executing)
_tmpfile=$(mktemp "${TMPDIR:-/tmp}/rustup_install.XXXXXX")
if curl -sSf https://sh.rustup.rs -o "$_tmpfile" 2>/dev/null; then
_expected="${RUSTUP_INSTALLER_SHA256:-}"
if [[ -n "$_expected" ]]; then
if verify_sha256 "$_tmpfile" "$_expected"; then
msg_ok "[!] rustup installer sha256 verified"
sh "$_tmpfile" -y >/dev/null 2>&1
else
msg_err "[!] rustup installer sha256 mismatch; refusing to execute"
rm -f "$_tmpfile"
return 1
fi
else
sh "$_tmpfile" -y >/dev/null 2>&1
fi
else
msg_warn "[!] Failed to download rustup installer"
fi
rm -f "$_tmpfile"
# shellcheck source=/dev/null
source "${HOME}/.cargo/env" 2>/dev/null || true
cargo install smugglex &>/dev/null
# Install uv via downloaded script (verify before executing)
_tmpfile=$(mktemp "${TMPDIR:-/tmp}/uv_install.XXXXXX")
if curl -LsSf https://astral.sh/uv/install.sh -o "$_tmpfile" 2>/dev/null; then
_expected="${UV_INSTALLER_SHA256:-}"
if [[ -n "$_expected" ]]; then
if verify_sha256 "$_tmpfile" "$_expected"; then
msg_ok "[!] uv installer sha256 verified"
sh "$_tmpfile" &>/dev/null
else
msg_err "[!] uv installer sha256 mismatch; refusing to execute"
rm -f "$_tmpfile"
return 1
fi
else
sh "$_tmpfile" &>/dev/null
fi
else
msg_warn "[!] Failed to download uv installer"
fi
rm -f "$_tmpfile"
# shellcheck source=/dev/null
source "${HOME}/.local/bin/env" 2>/dev/null || export PATH="${HOME}/.local/bin:$PATH"
uv tool update-shell &>/dev/null || true
# Install shodan CLI via uv
uv tool install shodan --force &>/dev/null || uv tool upgrade shodan &>/dev/null || true
}
# Non-fatal error trap: log and continue
trap 'rc=$?; ts=$(date +"%Y-%m-%d %H:%M:%S"); cmd=${BASH_COMMAND}; loc_ln=${BASH_LINENO[0]:-0}; msg="[$ts] install.sh ERR($rc) @ line ${loc_ln} :: ${cmd}"; if [[ -n "${LOGFILE:-}" ]]; then echo "$msg" >>"$LOGFILE"; else echo "$msg" >&2; fi' ERR
# -------------------------------
# Minimal UI helpers (classic style)
# -------------------------------
header() { printf "%bRunning: %s%b\n" "$bblue" "$1" "$reset"; }
msg_run() { printf "%b%s%b\n" "$yellow" "$1" "$reset"; }
msg_ok() { printf "%b%s%b\n" "$bgreen" "$1" "$reset"; }
msg_warn() { printf "%b%s%b\n" "$yellow" "$1" "$reset"; }
msg_err() { printf "%b%s%b\n" "$red" "$1" "$reset"; }
with_spinner() {
local _msg="$1"
shift
if [[ $DRY_RUN == "true" ]]; then
printf "%s\n" "[DRY-RUN] ${_msg}"
printf "%s\n" "[DRY-RUN] $*"
return 0
fi
if [[ $VERBOSE == "true" ]]; then
[[ -n $_msg ]] && printf "%s\n" "$_msg"
"$@"
return $?
fi
if [[ ! -t 1 ]]; then
[[ -n $_msg ]] && printf "%s ... " "$_msg"
"$@" >/dev/null 2>&1
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
printf "done\n"
else
printf "failed\n"
fi
return $exit_code
fi
local spinner="|/-\\"
local spinner_len=4
local i=0
[[ -n $_msg ]] && printf "%s " "$_msg"
"$@" &
local cmd_pid=$!
while kill -0 "$cmd_pid" 2>/dev/null; do
printf "\r%s %s" "$_msg" "${spinner:i:1}"
i=$(((i + 1) % spinner_len))
sleep 0.1
done
wait "$cmd_pid"
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
printf "\r%s done\n" "$_msg"
else
printf "\r%s failed\n" "$_msg"
fi
return $exit_code
}
# Basic network precheck
check_network() {
printf "%bRunning: Network precheck%b\n" "$bblue" "$reset"
local _net_ok=true
# Silence successful output; show message only on failure. Use q_to to respect --verbose.
if ! q_to 5 bash -lc 'getent hosts github.com >/dev/null 2>&1 || dig +short github.com >/dev/null 2>&1 || nslookup github.com >/dev/null 2>&1'; then
printf "%b[!] DNS resolution for github.com failed. Check your network.%b\n" "$bred" "$reset"
_net_ok=false
fi
if ! q_to 10 curl -I -s https://github.com >/dev/null 2>&1; then
printf "%b[!] HTTPS connectivity to github.com failed. Installer may fail.%b\n" "$yellow" "$reset"
_net_ok=false
fi
if [[ $_net_ok == true ]]; then
printf "%bNetwork OK%b\n" "$bgreen" "$reset"
fi
# Warn about low disk space (installer needs ~5GB free for Go cache, tools, repos)
local _avail_mb
_avail_mb=$(df -m "${HOME}" 2>/dev/null | awk 'NR==2{print $4}')
if [[ -n ${_avail_mb:-} ]] && (( _avail_mb < 5120 )); then
printf "%b[!] Low disk space: only %s MB free on %s. Installation needs ~5GB. Some tools may fail.%b\n" "$bred" "$_avail_mb" "$HOME" "$reset"
fi
# Warn about low memory (Go compilation needs at least 1GB)
if [[ -f /proc/meminfo ]]; then
local _mem_total_kb
_mem_total_kb=$(awk '/^MemTotal:/{print $2}' /proc/meminfo 2>/dev/null || true)
if [[ -n ${_mem_total_kb:-} ]] && (( _mem_total_kb < 1048576 )); then
printf "%b[!] Low memory: %s MB total. Go/Rust compilation may fail. Consider adding swap.%b\n" "$yellow" "$((_mem_total_kb / 1024))" "$reset"
fi
fi
}
# Check Bash version
BASH_VERSION_NUM=$(bash --version | awk 'NR==1{print $4}' | cut -d'.' -f1)
if [[ $BASH_VERSION_NUM -lt 4 ]]; then
printf "%bYour Bash version is lower than 4, please update.%b\n" "$bred" "$reset"
if [[ $IS_MAC == "True" ]]; then
printf "%bFor macOS, run 'brew install bash' and rerun the installer in a new terminal.%b\n" "$yellow" "$reset"
fi
exit 1
fi
# Declare Go tools: name -> module path (always installed @latest)
declare -A gotools=(
["gf"]="github.com/tomnomnom/gf"
["brutespray"]="github.com/x90skysn3k/brutespray/v2"
["qsreplace"]="github.com/tomnomnom/qsreplace"
["ffuf"]="github.com/ffuf/ffuf/v2"
["github-subdomains"]="github.com/gwen001/github-subdomains"
["gitlab-subdomains"]="github.com/gwen001/gitlab-subdomains"
["nuclei"]="github.com/projectdiscovery/nuclei/v3/cmd/nuclei"
["anew"]="github.com/tomnomnom/anew"
["notify"]="github.com/projectdiscovery/notify/cmd/notify"
["unfurl"]="github.com/tomnomnom/unfurl"
["httpx"]="github.com/projectdiscovery/httpx/cmd/httpx"
["github-endpoints"]="github.com/gwen001/github-endpoints"
["dnsx"]="github.com/projectdiscovery/dnsx/cmd/dnsx"
["subjs"]="github.com/lc/subjs"
["Gxss"]="github.com/KathanP19/Gxss"
["katana"]="github.com/projectdiscovery/katana/cmd/katana"
["crlfuzz"]="github.com/dwisiswant0/crlfuzz/cmd/crlfuzz"
["dalfox"]="github.com/hahwul/dalfox/v2"
["puredns"]="github.com/d3mondev/puredns/v2"
["interactsh-client"]="github.com/projectdiscovery/interactsh/cmd/interactsh-client"
["analyticsrelationships"]="github.com/Josue87/analyticsrelationships"
["gotator"]="github.com/Josue87/gotator"
["roboxtractor"]="github.com/Josue87/roboxtractor"
["mapcidr"]="github.com/projectdiscovery/mapcidr/cmd/mapcidr"
["cdncheck"]="github.com/projectdiscovery/cdncheck/cmd/cdncheck"
["asnmap"]="github.com/projectdiscovery/asnmap/cmd/asnmap"
["dnstake"]="github.com/pwnesia/dnstake/cmd/dnstake"
["tlsx"]="github.com/projectdiscovery/tlsx/cmd/tlsx"
["gitdorks_go"]="github.com/damit5/gitdorks_go"
["smap"]="github.com/s0md3v/smap/cmd/smap"
["dsieve"]="github.com/trickest/dsieve"
["inscope"]="github.com/tomnomnom/hacks/inscope"
["enumerepo"]="github.com/trickest/enumerepo"
["Web-Cache-Vulnerability-Scanner"]="github.com/Hackmanit/Web-Cache-Vulnerability-Scanner"
["subfinder"]="github.com/projectdiscovery/subfinder/v2/cmd/subfinder"
["hakip2host"]="github.com/hakluke/hakip2host"
["mantra"]="github.com/brosck/mantra"
["crt"]="github.com/cemulus/crt"
["s3scanner"]="github.com/sa7mon/s3scanner"
["nmapurls"]="github.com/sdcampbell/nmapurls"
["naabu"]="github.com/projectdiscovery/naabu/v2/cmd/naabu"
["shortscan"]="github.com/bitquark/shortscan/cmd/shortscan"
["hakoriginfinder"]="github.com/hakluke/hakoriginfinder"
["sourcemapper"]="github.com/denandz/sourcemapper"
["jsluice"]="github.com/BishopFox/jsluice/cmd/jsluice"
["sj"]="github.com/BishopFox/sj"
["urlfinder"]="github.com/projectdiscovery/urlfinder/cmd/urlfinder"
["cent"]="github.com/xm1k3/cent/v2"
["csprecon"]="github.com/edoardottt/csprecon/cmd/csprecon"
["exifray"]="github.com/mmarting/exifray"
["VhostFinder"]="github.com/wdahlenburg/VhostFinder"
["misconfig-mapper"]="github.com/intigriti/misconfig-mapper/cmd/misconfig-mapper"
["grpcurl"]="github.com/fullstorydev/grpcurl/cmd/grpcurl"
["toxicache"]="github.com/xhzeem/toxicache"
["favirecon"]="github.com/edoardottt/favirecon/cmd/favirecon"
["second-order"]="github.com/mhmdiaa/second-order"
["TInjA"]="github.com/Hackmanit/TInjA"
["nerva"]="github.com/praetorian-inc/nerva/cmd/nerva"
["brutus"]="github.com/praetorian-inc/brutus/cmd/brutus"
["julius"]="github.com/praetorian-inc/julius/cmd/julius"
["titus"]="github.com/praetorian-inc/titus/cmd/titus"
)
# Declare uv tool-managed Python tools and their GitHub paths
declare -A pipxtools=(
["dnsvalidator"]="vortexau/dnsvalidator"
["interlace"]="pry0cc/interlace"
["wafw00f"]="EnableSecurity/wafw00f"
["commix"]="commixproject/commix"
["waymore"]="xnl-h4ck3r/waymore"
["urless"]="xnl-h4ck3r/urless"
["ghauri"]="r0oth3x49/ghauri"
["xnLinkFinder"]="xnl-h4ck3r/xnLinkFinder"
["xnldorker"]="xnl-h4ck3r/xnldorker"
["porch-pirate"]="MandConsultingGroup/porch-pirate"
["p1radup"]="iambouali/p1radup"
["subwiz"]="hadriansecurity/subwiz"
["arjun"]="s0md3v/Arjun"
["gqlspection"]="doyensec/GQLSpection"
["postleaksNg"]="six2dez/postleaksNG"
["cewler"]="roys/cewler"
["fray"]="dalisecurity/fray"
)
# Declare repositories and their paths
declare -A repos=(
["dorks_hunter"]="six2dez/dorks_hunter"
["gf"]="tomnomnom/gf"
["Gf-Patterns"]="1ndianl33t/Gf-Patterns"
["sus_params"]="g0ldencybersec/sus_params"
["CMSeeK"]="Tuhinshubhra/CMSeeK"
["massdns"]="blechschmidt/massdns"
["testssl.sh"]="testssl/testssl.sh"
["JSA"]="w9w/JSA"
["cloud_enum"]="initstring/cloud_enum"
["ultimate-nmap-parser"]="shifty0g/ultimate-nmap-parser"
["gitdorks_go"]="damit5/gitdorks_go"
["Web-Cache-Vulnerability-Scanner"]="Hackmanit/Web-Cache-Vulnerability-Scanner"
["regulator"]="cramppet/regulator"
["ghleaks"]="dinosn/ghleaks"
["trufflehog"]="trufflesecurity/trufflehog"
["nomore403"]="devploit/nomore403"
["SwaggerSpy"]="UndeadSec/SwaggerSpy"
["LeakSearch"]="JoelGMSec/LeakSearch"
["ffufPostprocessing"]="Damian89/ffufPostprocessing"
["misconfig-mapper"]="intigriti/misconfig-mapper"
["Spoofy"]="MattKeeley/Spoofy"
["msftrecon"]="Arcanum-Sec/msftrecon"
["Scopify"]="Arcanum-Sec/Scopify"
["EmailHarvester"]="maldevel/EmailHarvester"
["reconftw_ai"]="six2dez/reconftw_ai"
["gato"]="praetorian-inc/gato"
["SSTImap"]="vladko312/SSTImap"
)
# Function to display the banner
function banner() {
printf "\n"
cat <<EOF
██▀███ ▓█████ ▄████▄ ▒█████ ███▄ █ █████▒▄▄▄█████▓ █ █░
▓██ ▒ ██▒▓█ ▀ ▒██▀ ▀█ ▒██▒ ██▒ ██ ▀█ █ ▓██ ▒ ▓ ██▒ ▓▒▓█░ █ ░█░
▓██ ░▄█ ▒▒███ ▒▓█ ▄ ▒██░ ██▒▓██ ▀█ ██▒▒████ ░ ▒ ▓██░ ▒░▒█░ █ ░█
▒██▀▀█▄ ▒▓█ ▄ ▒▓▓▄ ▄██▒▒██ ██░▓██▒ ▐▌██▒░▓█▒ ░ ░ ▓██▓ ░ ░█░ █ ░█
░██▓ ▒██▒░▒████▒▒ ▓███▀ ░░ ████▓▒░▒██░ ▓██░░▒█░ ▒██▒ ░ ░░██▒██▓
░ ▒▓ ░▒▓░░░ ▒░ ░░ ░▒ ▒ ░░ ▒░▒░▒░ ░ ▒░ ▒ ▒ ▒ ░ ▒ ░░ ░ ▓░▒ ▒
░▒ ░ ▒░ ░ ░ ░ ░ ▒ ░ ▒ ▒░ ░ ░░ ░ ▒░ ░ ░ ▒ ░ ░
░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░
░ ░ ░░ ░ ░ ░ ░ ░
${reconftw_version} by @six2dez
EOF
}
# Clone a GitHub repo with retry + cleanup between attempts.
# Falls back to full clone if --filter=blob:none is unsupported.
clone_repo() {
local gh_path="$1" dest="$2"
local url="https://github.com/${gh_path}"
local n=0 max=3 delay=3
while true; do
rm -rf "$dest" 2>/dev/null
if q_to 180 git clone --filter="blob:none" "$url" "$dest"; then
return 0
fi
# Fallback: try full clone (in case server/git version doesn't support partial clone)
rm -rf "$dest" 2>/dev/null
if q_to 180 git clone "$url" "$dest"; then
return 0
fi
n=$((n + 1))
if ((n >= max)); then return 1; fi
sleep $((delay * n))
done
}
function interlace_tool_python() {
local tool_python
tool_python="$(uv tool dir 2>/dev/null)/interlace/bin/python"
[[ -x "$tool_python" ]] || return 1
printf '%s\n' "$tool_python"
}
function interlace_colorclass_codes_path() {
local tool_python
tool_python="$(interlace_tool_python)" || return 1
"$tool_python" -c 'import importlib.util, os; spec = importlib.util.find_spec("colorclass"); print("" if spec is None or spec.origin is None else os.path.join(os.path.dirname(spec.origin), "codes.py"))' 2>/dev/null
}
function interlace_colorclass_imports_ok() {
local tool_python
tool_python="$(interlace_tool_python)" || return 1
"$tool_python" -c 'import colorclass' >/dev/null 2>&1
}
function ensure_interlace_colorclass_healthy() {
local cc_codes
if interlace_colorclass_imports_ok; then
return 0
fi
cc_codes="$(interlace_colorclass_codes_path)" || return 1
[[ -n "$cc_codes" && -f "$cc_codes" ]] || return 1
if grep -q 'from collections import Mapping' "$cc_codes"; then
sed -i.bak 's/from collections import Mapping/from collections.abc import Mapping/' "$cc_codes" || return 1
rm -f "${cc_codes}.bak" 2>/dev/null || true
fi
interlace_colorclass_imports_ok
}
# Function to install Go tools
function install_tools() {
header "Installing Golang tools (${#gotools[@]})"
# Force module-mode resolution so vendored or GOPATH-mode environments
# don't break go install for tools whose modules use SIV (e.g. /v2, /v3).
export GOFLAGS="-mod=mod"
export GO111MODULE="on"
# Load optional tool pins from ./tools.lock (D-12 / SEC-04).
# Each line: <binary>=<module>@<version>. Comments (#) and blank lines ignored.
# If a tool listed here is also in $gotools, the lock entry wins; otherwise
# `go install @latest` is used. Errors loading tools.lock are non-fatal.
declare -A pinned_tools=()
local _lockfile="${SCRIPTPATH:-$(pwd)}/tools.lock"
if [[ -f "$_lockfile" ]]; then
local _key _val
while IFS='=' read -r _key _val; do
# Strip surrounding whitespace; skip blanks/comments
_key="${_key#"${_key%%[![:space:]]*}"}"
_key="${_key%"${_key##*[![:space:]]}"}"
[[ -z "$_key" || "$_key" == \#* ]] && continue
_val="${_val#"${_val%%[![:space:]]*}"}"
_val="${_val%"${_val##*[![:space:]]}"}"
[[ -z "$_val" ]] && continue
pinned_tools["$_key"]="$_val"
done < "$_lockfile"
msg_ok "Loaded ${#pinned_tools[@]} pin(s) from tools.lock"
fi
local go_step=0
local failed_tools=()
local total_go=${#gotools[@]}
local go_ok=0 go_skip=0 go_fail=0
for gotool in "${!gotools[@]}"; do
((++go_step))
# Pinned version wins; otherwise fall through to @latest.
local _module_at_version
if [[ -n "${pinned_tools[$gotool]:-}" ]]; then
_module_at_version="${pinned_tools[$gotool]}"
else
_module_at_version="${gotools[$gotool]}@latest"
fi
# Always run go install so already-present binaries also get updated.
# argv form (not bash -lc) so arr values are data, not shell syntax.
if q go install -v "$_module_at_version"; then
((++go_ok))
msg_ok "[$go_step/$total_go] ${gotool} installed"
else
# If the binary is already present, the upgrade failed but the tool still works.
# Treat this as a warning rather than a hard failure.
if command -v "$gotool" >/dev/null 2>&1; then
((++go_skip))
msg_warn "[$go_step/$total_go] ${gotool} upgrade failed (existing binary kept)"
else
failed_tools+=("$gotool")
((++go_fail))
double_check=true
msg_err "[$go_step/$total_go] ${gotool} failed"
fi
fi
done
header "Installing uv tools (${#pipxtools[@]})"
local pipx_step=0
local failed_pipx_tools=()
local total_px=${#pipxtools[@]}
local px_ok=0 px_skip=0 px_fail=0
for pipxtool in "${!pipxtools[@]}"; do
((++pipx_step))
# Default to git+https for tools that are not published on PyPI.
# fray is published on PyPI and its old GitHub install URL is no longer available.
local tool_url="git+https://github.com/${pipxtools[$pipxtool]}"
if [[ "$pipxtool" == "fray" ]]; then
tool_url="fray"
fi
# Prepare arguments array
local tool_args=()
# Special case for postleaksNg to fix jellyfish dependency issue
if [[ "$pipxtool" == "postleaksNg" ]]; then
tool_args+=("--with" "jellyfish>=1.1.3")
fi
# Always force install/reinstall from the git URL
# This handles both initial install and upgrades correctly
if q uv tool install "${tool_args[@]}" "$tool_url" --force; then
if [[ "$pipxtool" == "interlace" ]] && ! ensure_interlace_colorclass_healthy; then
failed_pipx_tools+=("$pipxtool")
((++px_fail))
double_check=true
msg_err "[$pipx_step/$total_px] ${pipxtool} failed health check"
continue
fi
((++px_ok))
msg_ok "[$pipx_step/$total_px] ${pipxtool} ready"
else
failed_pipx_tools+=("$pipxtool")
((++px_fail))
double_check=true
msg_err "[$pipx_step/$total_px] ${pipxtool} failed"
fi
done
header "Installing repositories (${#repos[@]})"
local repos_step=0
local failed_repos=()
local total_repo=${#repos[@]}
local repo_ok=0 repo_skip=0 repo_fail=0
install_repo_requirements() {
if [[ -s "requirements.txt" ]]; then
if [[ ! -d "venv" ]]; then
uv venv venv &>/dev/null
fi
if ! uv pip install --upgrade -r requirements.txt --python venv/bin/python3 &>/dev/null; then
return 1
fi
if [[ $1 == "dorks_hunter" ]]; then
uv pip install --upgrade xnldorker --python venv/bin/python3 &>/dev/null || true
fi
fi
return 0
}
for repo in "${!repos[@]}"; do
((++repos_step))
if [[ $upgrade_tools == "false" ]]; then
if [[ -d "${dir}/${repo}" ]]; then
# Keep Python deps updated even when repository sync is skipped.
if ! ( cd "${dir}/${repo}" && install_repo_requirements "$repo" ); then
msg_err "[$repos_step/$total_repo] $repo: pip requirements failed"
failed_repos+=("$repo")
((++repo_fail))
double_check=true
fi
((++repo_skip))
msg_warn "[$repos_step/$total_repo] $repo already present at ${dir}/${repo}"
continue
fi
fi
# Clone the repository (check for .git to detect incomplete clones)
if [[ ! -d "${dir}/${repo}/.git" ]]; then
# Remove leftover directory from a previously failed clone attempt
[[ -d "${dir}/${repo}" ]] && rm -rf "${dir}/${repo}"
msg_run "[$repos_step/${#repos[@]}] $repo (clone)"
clone_repo "${repos[$repo]}" "${dir}/${repo}"
exit_status=$?
if [[ $exit_status -ne 0 ]]; then
msg_err "[$repos_step/$total_repo] $repo clone failed"
failed_repos+=("$repo")
((++repo_fail))
double_check=true
continue
fi
((++repo_ok))
fi
# Navigate to the repository directory
cd "${dir}/${repo}" || {
msg_err "[$repos_step/$total_repo] $repo: cannot enter ${dir}/${repo}"
failed_repos+=("$repo")
((++repo_fail))
double_check=true
continue
}
# Update origin URL if the entry in the repos array has changed (e.g. org rename)
local _expected_url="https://github.com/${repos[$repo]}"
local _current_url
_current_url=$(git remote get-url origin 2>/dev/null || echo "")
if [[ -n "$_current_url" && "${_current_url%.git}" != "${_expected_url%.git}" ]]; then
git remote set-url origin "$_expected_url" &>/dev/null || true
fi
# Return to default branch if stuck in detached HEAD (e.g. from a previous tag checkout)
if ! git symbolic-ref -q HEAD &>/dev/null; then
local _default_branch
_default_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')
# Fallback to network lookup if local ref is missing
[[ -z "$_default_branch" ]] && _default_branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/{print $NF}')
[[ -n "$_default_branch" ]] && git checkout "$_default_branch" &>/dev/null || true
fi
# Pull the latest changes
msg_run "[$repos_step/${#repos[@]}] $repo (pull)"
retry 3 3 q_to 60 git pull
exit_status=$?
if [[ $exit_status -ne 0 ]]; then
msg_err "[$repos_step/$total_repo] $repo pull failed"
failed_repos+=("$repo")
((++repo_fail))
double_check=true
continue
fi
# Install requirements inside a virtual environment
if ! install_repo_requirements "$repo"; then
msg_err "[$repos_step/$total_repo] $repo: pip requirements failed"
failed_repos+=("$repo")
((++repo_fail))
double_check=true
fi
# Special handling for certain repositories (verify build exit codes)
case "$repo" in
"massdns")
if ! make &>/dev/null; then
msg_warn "[$repos_step/$total_repo] $repo: make failed"
else
strip -s bin/massdns 2>/dev/null || true
$SUDO cp bin/massdns /usr/local/bin/ &>/dev/null
fi
;;
"ghleaks")
if ! go build -o ghleaks . &>/dev/null; then
msg_warn "[$repos_step/$total_repo] $repo: go build failed"
else
chmod +x ./ghleaks
fi
;;
"nomore403")
if ! go build &>/dev/null; then
msg_warn "[$repos_step/$total_repo] $repo: go build failed"
else
chmod +x ./nomore403
fi
;;
"ffufPostprocessing")
git reset --hard origin/master &>/dev/null
git pull &>/dev/null
if ! go build -o ffufPostprocessing main.go &>/dev/null; then
msg_warn "[$repos_step/$total_repo] $repo: go build failed"
else
chmod +x ./ffufPostprocessing
fi
;;
"trufflehog")
go install github.com/trufflesecurity/trufflehog/v3@latest &>/dev/null || msg_warn "[$repos_step/$total_repo] $repo: go install failed"
;;
"gato")
if [[ ! -d "venv" ]]; then
uv venv venv &>/dev/null || true
fi
uv pip install --upgrade -e . --python venv/bin/python3 &>/dev/null || true
;;
"SSTImap")
if [[ ! -d "venv" ]]; then
uv venv venv &>/dev/null || true
fi
uv pip install --upgrade -r requirements.txt --python venv/bin/python3 &>/dev/null || true
;;
esac
# Copy gf patterns if applicable
if [[ $repo == "gf" ]]; then
cp -r examples "${HOME}/.gf" &>/dev/null || true
elif [[ $repo == "Gf-Patterns" ]]; then
cp ./*.json "${HOME}/.gf" &>/dev/null || true
elif [[ $repo == "sus_params" ]]; then
for f in ./gf-patterns/*.json; do
base=$(basename "$f")
dest="${HOME}/.gf/$base"
cat "$f" | anew -q "$dest" >/dev/null || true
done
fi
# Return to the main directory
cd "$dir" || {
msg_err "Failed to navigate back to directory '$dir'"
exit 1
}
msg_ok "[$repos_step/$total_repo] $repo ready"
done
# Initialize tool configs on first run
q command -v notify >/dev/null 2>&1 && q notify || true
q command -v subfinder >/dev/null 2>&1 && q subfinder || true
mkdir -p ${NUCLEI_TEMPLATES_PATH} &>/dev/null
#cent init -f &>/dev/null
#cent -p ${NUCLEI_TEMPLATES_PATH} &>/dev/null
# Installation summary
printf "\n%b--- Tool Installation Summary ---%b\n" "$bblue" "$reset"
printf " Go tools: %b%d OK%b, %d skipped, %b%d failed%b (of %d)\n" \
"$bgreen" "$go_ok" "$reset" "$go_skip" \
"$([[ $go_fail -gt 0 ]] && echo "$red" || echo "$bgreen")" "$go_fail" "$reset" "$total_go"
printf " uv tools: %b%d OK%b, %b%d failed%b (of %d)\n" \
"$bgreen" "$px_ok" "$reset" \
"$([[ $px_fail -gt 0 ]] && echo "$red" || echo "$bgreen")" "$px_fail" "$reset" "$total_px"
printf " Repos: %b%d OK%b, %d skipped, %b%d failed%b (of %d)\n" \
"$bgreen" "$repo_ok" "$reset" "$repo_skip" \
"$([[ $repo_fail -gt 0 ]] && echo "$red" || echo "$bgreen")" "$repo_fail" "$reset" "$total_repo"
local _total_fail=$(( go_fail + px_fail + repo_fail ))
if [[ $_total_fail -gt 0 ]]; then
printf "\n%bFailed items:%b\n" "$red" "$reset"
[[ ${#failed_tools[@]} -gt 0 ]] && printf " Go: %s\n" "${failed_tools[*]}"
[[ ${#failed_pipx_tools[@]} -gt 0 ]] && printf " uv: %s\n" "${failed_pipx_tools[*]}"
[[ ${#failed_repos[@]} -gt 0 ]] && printf " Repos: %s\n" "${failed_repos[*]}"
printf "\n%bRe-run install.sh to retry failed items.%b\n" "$yellow" "$reset"
fi
}
# Function to reset git proxy settings
function reset_git_proxies() {
git config --global --unset http.proxy || true
git config --global --unset https.proxy || true
export GIT_TERMINAL_PROMPT=0
}
# Function to check for updates
function check_updates() {
printf "%bRunning: Looking for new reconFTW version%b\n" "$bblue" "$reset"
if { [[ -n $TIMEOUT_CMD ]] && $TIMEOUT_CMD 10 git fetch; } || git fetch; then
local BRANCH
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")
HEADHASH=$(git rev-parse HEAD 2>/dev/null || true)
# Skip auto-update if no upstream (detached HEAD or no tracking branch)
if ! git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
printf "%bNo upstream configured (detached HEAD). Skipping auto-update.%b\n" "$yellow" "$reset"
return 0
fi
UPSTREAMHASH=$(git rev-parse "@{u}")
if [[ $HEADHASH != "$UPSTREAMHASH" ]]; then
local cfg_backup="" cfg_backup_ts=""
if git status --porcelain | grep -q .; then
if [[ $FORCE_UPDATE == "true" ]]; then
printf "%bLocal changes detected; forcing update.%b\n" "$yellow" "$reset"
else
printf "%bLocal changes detected. Skipping auto-update. Re-run with --force-update to override.%b\n" "$yellow" "$reset"
return 0
fi
fi
printf "%bA new version is available. Updating...%b\n" "$yellow" "$reset"
if git status --porcelain | grep -q 'reconftw.cfg$'; then
cfg_backup_ts=$(date +%Y%m%d_%H%M%S)
cfg_backup="reconftw.cfg.bak.${cfg_backup_ts}"
cp reconftw.cfg "$cfg_backup"
printf "%breconftw.cfg has been backed up to %s%b\n" "$yellow" "$cfg_backup" "$reset"
fi
git stash --include-untracked &>/dev/null || true
run_to 60 git pull &>/dev/null
git stash pop &>/dev/null || true
printf "%bUpdated! Running the new installer version...%b\n" "$bgreen" "$reset"
# Show config diff against new default
if [[ -n $cfg_backup && -f reconftw.cfg ]]; then
mkdir -p .tmp
cfg_diff_file=".tmp/reconftw_cfg_diff_${cfg_backup_ts}.patch"
if diff -u "$cfg_backup" reconftw.cfg >"$cfg_diff_file"; then
printf "%bConfig unchanged between versions (diff: %s).%b\n" "$yellow" "$cfg_diff_file" "$reset"
else
printf "%bConfig differences saved to %s (old -> new).%b\n" "$yellow" "$cfg_diff_file" "$reset"
fi
fi
else
printf "%breconFTW is already up to date!%b\n" "$bgreen" "$reset"
# If config is locally modified, still provide a diff against current default
if ! git diff --quiet -- reconftw.cfg; then
local cfg_diff_ts cfg_diff_file
cfg_diff_ts=$(date +%Y%m%d_%H%M%S)
mkdir -p .tmp
cfg_diff_file=".tmp/reconftw_cfg_diff_${cfg_diff_ts}.patch"
if git diff HEAD --unified -- reconftw.cfg >"$cfg_diff_file"; then
printf "%bLocal reconftw.cfg differs from default; diff saved to %s.%b\n" "$yellow" "$cfg_diff_file" "$reset"
fi
fi
fi
else
printf "\n%b[!] Unable to check for updates.%b\n" "$bred" "$reset"
fi
}
# Function to install Golang
function install_golang_version() {
local version="go1.23.6"
local latest_version
latest_version=$(curl -s https://go.dev/VERSION?m=text | head -1 || echo "go1.23.6")
if [[ $latest_version == g* ]]; then
version="$latest_version"
fi
printf "%bRunning: Installing/Updating Golang(%s) %b\n" "$bblue" "$version" "$reset"
if [[ $install_golang == "true" ]]; then
local current_version=""
if command -v go &>/dev/null; then
current_version="$(go version | awk '{print $3}')"
fi
if [[ -n $current_version && $version == "$current_version" ]]; then
printf "%bGolang is already installed and up to date.%b\n" "$bgreen" "$reset"
else
local archive_suffix=""
case "$ARCH" in
arm64 | aarch64)
if [[ $IS_MAC == "True" ]]; then
archive_suffix="darwin-arm64"
else
archive_suffix="linux-arm64"
fi
;;
armv6l | armv7l)
archive_suffix="linux-armv6l"
;;
amd64 | x86_64)
if [[ $IS_MAC == "True" ]]; then
archive_suffix="darwin-amd64"
else
archive_suffix="linux-amd64"
fi
;;
*)
msg_err "[!] Unsupported architecture. Please install go manually."
return 1
;;
esac
local archive_url="https://dl.google.com/go/${version}.${archive_suffix}.tar.gz"
local archive_path="/tmp/${version}.${archive_suffix}.tar.gz"
if ! wget "$archive_url" -O "$archive_path" &>/dev/null; then
msg_err "[!] Failed to download Golang archive from ${archive_url}"
return 1
fi
# Verify SHA256 checksum from go.dev
local expected_sha256
expected_sha256=$(curl -sL "${archive_url}.sha256" 2>/dev/null || true)
if [[ -n $expected_sha256 ]]; then
local actual_sha256
if command -v sha256sum &>/dev/null; then
actual_sha256=$(sha256sum "$archive_path" | awk '{print $1}')
elif command -v shasum &>/dev/null; then
actual_sha256=$(shasum -a 256 "$archive_path" | awk '{print $1}')
fi
if [[ -n ${actual_sha256:-} && $actual_sha256 != "$expected_sha256" ]]; then
msg_err "[!] SHA256 checksum mismatch for Go archive (expected: ${expected_sha256}, got: ${actual_sha256})"
rm -f "$archive_path"
return 1
fi
else
msg_warn "[!] Could not fetch SHA256 checksum for Go archive; skipping verification"
fi
local tmp_unpack
tmp_unpack=$(mktemp -d 2>/dev/null || mktemp -d -t goinstall)
trap 'rm -rf "$tmp_unpack" "$archive_path"' RETURN
if ! tar -C "$tmp_unpack" -xzf "$archive_path" &>/dev/null; then
msg_err "[!] Failed to extract ${archive_path}"
return 1
fi
if [[ ! -d "${tmp_unpack}/go" ]]; then
msg_err "[!] Extracted archive missing 'go' directory"
return 1
fi
local go_backup=""
if [[ -d /usr/local/go ]]; then
go_backup="/usr/local/go.reconftw.$(date +%s)"
if ! $SUDO mv /usr/local/go "$go_backup" &>/dev/null; then
msg_warn "[!] Unable to backup existing /usr/local/go; attempting in-place overwrite."
go_backup=""
if ! ($SUDO rm -rf /usr/local/go &>/dev/null); then
msg_warn "[!] Failed to remove existing /usr/local/go; installation may overwrite partially."
fi
fi
fi
if ! $SUDO mv "${tmp_unpack}/go" /usr/local/go; then
msg_err "[!] Failed to move Golang into /usr/local/go"
if [[ -n $go_backup && -d $go_backup ]]; then
$SUDO mv "$go_backup" /usr/local/go &>/dev/null || msg_warn "[!] Unable to restore previous Golang installation from backup."
fi
return 1
fi
if [[ -n $go_backup ]]; then