77from bbot .errors import *
88from bbot import __version__
99from bbot .logger import log_to_stderr
10- from bbot .core .helpers .misc import chain_lists , rm_rf
10+ from bbot .core .helpers .misc import chain_lists
1111
1212
1313if multiprocessing .current_process ().name == "MainProcess" :
@@ -56,6 +56,10 @@ async def _main():
5656 return
5757 # ensure arguments (-c config options etc.) are valid
5858 options = preset .args .parsed
59+ # apply CLI log level options (e.g. --debug/--verbose/--silent) to the
60+ # global core logger even for CLI-only commands (like --install-all-deps)
61+ # that don't construct a full Scanner.
62+ preset .apply_log_level (apply_core = True )
5963
6064 # print help if no arguments
6165 if len (sys .argv ) == 1 :
@@ -90,7 +94,8 @@ async def _main():
9094 preset ._default_output_modules = options .output_modules
9195 preset ._default_internal_modules = []
9296
93- preset .bake ()
97+ # Bake a temporary copy of the preset so that flags correctly enable their associated modules before listing them
98+ preset = preset .bake ()
9499
95100 # --list-modules
96101 if options .list_modules :
@@ -144,59 +149,67 @@ async def _main():
144149 print (row )
145150 return
146151
147- try :
148- scan = Scanner (preset = preset )
149- except (PresetAbortError , ValidationError ) as e :
150- log .warning (str (e ))
152+ baked_preset = preset .bake ()
153+
154+ # --current-preset / --current-preset-full
155+ if options .current_preset or options .current_preset_full :
156+ # Ensure we always have a human-friendly description. Prefer an
157+ # explicit scan_name if present, otherwise fall back to the
158+ # preset name (e.g. "bbot_cli_main").
159+ if not baked_preset .description :
160+ if baked_preset .scan_name :
161+ baked_preset .description = str (baked_preset .scan_name )
162+ elif baked_preset .name :
163+ baked_preset .description = str (baked_preset .name )
164+ if options .current_preset_full :
165+ print (baked_preset .to_yaml (full_config = True ))
166+ else :
167+ print (baked_preset .to_yaml ())
168+ sys .exit (0 )
151169 return
152170
171+ # deadly modules (no scan required yet)
153172 deadly_modules = [
154- m for m in scan . preset . scan_modules if "deadly" in preset .preloaded_module (m ).get ("flags" , [])
173+ m for m in baked_preset . scan_modules if "deadly" in baked_preset .preloaded_module (m ).get ("flags" , [])
155174 ]
156175 if deadly_modules and not options .allow_deadly :
157176 log .hugewarning (f"You enabled the following deadly modules: { ',' .join (deadly_modules )} " )
158177 log .hugewarning ("Deadly modules are highly intrusive" )
159178 log .hugewarning ("Please specify --allow-deadly to continue" )
160179 return False
161180
162- # --current-preset
163- if options .current_preset :
164- print (scan .preset .to_yaml ())
165- sys .exit (0 )
166- return
167-
168- # --current-preset-full
169- if options .current_preset_full :
170- print (scan .preset .to_yaml (full_config = True ))
171- sys .exit (0 )
181+ try :
182+ scan = Scanner (preset = baked_preset )
183+ except (PresetAbortError , ValidationError ) as e :
184+ log .warning (str (e ))
172185 return
173186
174187 # --install-all-deps
175188 if options .install_all_deps :
189+ # create a throwaway Scanner solely so that Preset.bake(scan) can perform find_and_replace() on all module configs so that placeholders like "#{BBOT_TOOLS}" are resolved before running Ansible tasks.
190+ from bbot .scanner import Scanner as _ScannerForDeps
191+
176192 preloaded_modules = preset .module_loader .preloaded ()
177- scan_modules = [k for k , v in preloaded_modules .items () if str (v .get ("type" , "" )) == "scan" ]
178- output_modules = [k for k , v in preloaded_modules .items () if str (v .get ("type" , "" )) == "output" ]
179- log .verbose ("Creating dummy scan with all modules + output modules for deps installation" )
180- dummy_scan = Scanner (preset = preset , modules = scan_modules , output_modules = output_modules )
181- dummy_scan .helpers .depsinstaller .force_deps = True
193+ modules_for_deps = [
194+ k for k , v in preloaded_modules .items () if str (v .get ("type" , "" )) in ("scan" , "output" )
195+ ]
196+
197+ # dummy scan used only for environment preparation
198+ dummy_scan = _ScannerForDeps (preset = preset )
199+
200+ helper = dummy_scan .helpers
182201 log .info ("Installing module dependencies" )
183- await dummy_scan .load_modules ()
184- log .verbose ("Running module setups" )
185- succeeded , hard_failed , soft_failed = await dummy_scan .setup_modules (deps_only = True )
186- # remove any leftovers from the dummy scan
187- rm_rf (dummy_scan .home , ignore_errors = True )
188- rm_rf (dummy_scan .temp_dir , ignore_errors = True )
202+ succeeded , failed = await helper .depsinstaller .install (* modules_for_deps )
189203 if succeeded :
190204 log .success (
191205 f"Successfully installed dependencies for { len (succeeded ):,} modules: { ',' .join (succeeded )} "
192206 )
193- if soft_failed or hard_failed :
194- failed = soft_failed + hard_failed
207+ if failed :
195208 log .warning (f"Failed to install dependencies for { len (failed ):,} modules: { ', ' .join (failed )} " )
196209 return False
197210 return True
198211
199- scan_name = str ( scan .name )
212+ await scan ._prep ( )
200213
201214 log .verbose ("" )
202215 log .verbose ("### MODULES ENABLED ###" )
@@ -205,12 +218,19 @@ async def _main():
205218 log .verbose (row )
206219
207220 scan .helpers .word_cloud .load ()
208- await scan ._prep ()
221+
222+ scan_name = str (scan .name )
209223
210224 if not options .dry_run :
211225 log .trace (f"Command: { ' ' .join (sys .argv )} " )
212226
213- if sys .stdin .isatty ():
227+ # In some environments (e.g. tests) stdin may be closed or not support isatty(). Treat those cases as non-interactive.
228+ try :
229+ stdin_is_tty = sys .stdin .isatty ()
230+ except (ValueError , io .UnsupportedOperation ):
231+ stdin_is_tty = False
232+
233+ if stdin_is_tty :
214234 # warn if any targets belong directly to a cloud provider
215235 if not scan .preset .strict_scope :
216236 for event in scan .target .seeds .event_seeds :
0 commit comments