souleyez 2.16.0__py3-none-any.whl → 2.26.0__py3-none-any.whl
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.
- souleyez/__init__.py +1 -1
- souleyez/assets/__init__.py +1 -0
- souleyez/assets/souleyez-icon.png +0 -0
- souleyez/core/msf_sync_manager.py +15 -5
- souleyez/core/tool_chaining.py +221 -29
- souleyez/detection/validator.py +4 -2
- souleyez/docs/README.md +2 -2
- souleyez/docs/user-guide/installation.md +14 -1
- souleyez/engine/background.py +25 -1
- souleyez/engine/result_handler.py +129 -0
- souleyez/integrations/siem/splunk.py +58 -11
- souleyez/main.py +103 -4
- souleyez/parsers/crackmapexec_parser.py +101 -43
- souleyez/parsers/dnsrecon_parser.py +50 -35
- souleyez/parsers/enum4linux_parser.py +101 -21
- souleyez/parsers/http_fingerprint_parser.py +319 -0
- souleyez/parsers/hydra_parser.py +56 -5
- souleyez/parsers/impacket_parser.py +123 -44
- souleyez/parsers/john_parser.py +47 -14
- souleyez/parsers/msf_parser.py +20 -5
- souleyez/parsers/nmap_parser.py +145 -28
- souleyez/parsers/smbmap_parser.py +69 -25
- souleyez/parsers/sqlmap_parser.py +72 -26
- souleyez/parsers/theharvester_parser.py +21 -13
- souleyez/plugins/gobuster.py +96 -3
- souleyez/plugins/http_fingerprint.py +592 -0
- souleyez/plugins/msf_exploit.py +6 -3
- souleyez/plugins/nuclei.py +41 -17
- souleyez/ui/interactive.py +130 -20
- souleyez/ui/setup_wizard.py +424 -58
- souleyez/ui/tool_setup.py +52 -52
- souleyez/utils/tool_checker.py +75 -13
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/METADATA +16 -3
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/RECORD +38 -34
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/WHEEL +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/entry_points.txt +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/licenses/LICENSE +0 -0
- {souleyez-2.16.0.dist-info → souleyez-2.26.0.dist-info}/top_level.txt +0 -0
souleyez/ui/setup_wizard.py
CHANGED
|
@@ -7,7 +7,9 @@ import time
|
|
|
7
7
|
import click
|
|
8
8
|
import getpass
|
|
9
9
|
import shutil
|
|
10
|
+
import subprocess
|
|
10
11
|
from pathlib import Path
|
|
12
|
+
from typing import List, Dict, Optional
|
|
11
13
|
from souleyez.ui.design_system import DesignSystem
|
|
12
14
|
|
|
13
15
|
|
|
@@ -26,16 +28,336 @@ def mark_wizard_completed():
|
|
|
26
28
|
WIZARD_STATE_FILE.touch()
|
|
27
29
|
|
|
28
30
|
|
|
31
|
+
def _check_disk_space(min_mb: int = 500) -> bool:
|
|
32
|
+
"""Check if there's enough disk space for installs."""
|
|
33
|
+
try:
|
|
34
|
+
usage = shutil.disk_usage('/')
|
|
35
|
+
free_mb = usage.free // (1024 * 1024)
|
|
36
|
+
return free_mb >= min_mb
|
|
37
|
+
except Exception:
|
|
38
|
+
return True # Assume OK if we can't check
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _configure_sudoers(tool_name: str, tool_path: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Add NOPASSWD sudoers entry for privileged tool.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
tool_name: Name of the tool (used for sudoers filename)
|
|
47
|
+
tool_path: Absolute path to tool binary
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
True if configured successfully, False otherwise
|
|
51
|
+
"""
|
|
52
|
+
# Skip if running as root
|
|
53
|
+
if os.geteuid() == 0:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
username = getpass.getuser()
|
|
57
|
+
sudoers_file = f"/etc/sudoers.d/{tool_name}"
|
|
58
|
+
sudoers_line = f"{username} ALL=(ALL) NOPASSWD: {tool_path}"
|
|
59
|
+
tmp_file = f"/tmp/sudoers_{tool_name}_{os.getpid()}"
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Write to temp file first
|
|
63
|
+
with open(tmp_file, 'w') as f:
|
|
64
|
+
f.write(sudoers_line + '\n')
|
|
65
|
+
|
|
66
|
+
# Validate syntax with visudo before applying
|
|
67
|
+
result = subprocess.run(
|
|
68
|
+
['sudo', 'visudo', '-c', '-f', tmp_file],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
timeout=30
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if result.returncode != 0:
|
|
74
|
+
click.echo(f" {click.style('!', fg='yellow')} Invalid sudoers syntax for {tool_name}, skipping")
|
|
75
|
+
os.unlink(tmp_file)
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
# Safe to move to sudoers.d
|
|
79
|
+
subprocess.run(['sudo', 'mv', tmp_file, sudoers_file], check=True, timeout=30)
|
|
80
|
+
subprocess.run(['sudo', 'chmod', '0440', sudoers_file], check=True, timeout=30)
|
|
81
|
+
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
except subprocess.TimeoutExpired:
|
|
85
|
+
click.echo(f" {click.style('!', fg='yellow')} Timeout configuring sudoers for {tool_name}")
|
|
86
|
+
return False
|
|
87
|
+
except Exception as e:
|
|
88
|
+
click.echo(f" {click.style('!', fg='yellow')} Error configuring sudoers for {tool_name}: {e}")
|
|
89
|
+
# Clean up temp file if it exists
|
|
90
|
+
if os.path.exists(tmp_file):
|
|
91
|
+
try:
|
|
92
|
+
os.unlink(tmp_file)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _install_desktop_shortcut():
|
|
99
|
+
"""
|
|
100
|
+
Install desktop shortcut for SoulEyez in Applications menu.
|
|
101
|
+
|
|
102
|
+
This runs silently during setup - any errors are ignored to not
|
|
103
|
+
disrupt the setup flow.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
applications_dir = Path.home() / '.local' / 'share' / 'applications'
|
|
107
|
+
icons_dir = Path.home() / '.local' / 'share' / 'icons'
|
|
108
|
+
desktop_file = applications_dir / 'souleyez.desktop'
|
|
109
|
+
icon_dest = icons_dir / 'souleyez.png'
|
|
110
|
+
|
|
111
|
+
# Skip if already installed
|
|
112
|
+
if desktop_file.exists():
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
# Create directories
|
|
116
|
+
applications_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
icons_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
|
|
119
|
+
# Find and copy icon
|
|
120
|
+
try:
|
|
121
|
+
# Try importlib.resources first (Python 3.9+)
|
|
122
|
+
try:
|
|
123
|
+
from importlib.resources import files
|
|
124
|
+
icon_source = files('souleyez.assets').joinpath('souleyez-icon.png')
|
|
125
|
+
with open(icon_source, 'rb') as src:
|
|
126
|
+
icon_data = src.read()
|
|
127
|
+
except (ImportError, TypeError, FileNotFoundError):
|
|
128
|
+
# Fallback: find icon relative to this file
|
|
129
|
+
icon_source = Path(__file__).parent.parent / 'assets' / 'souleyez-icon.png'
|
|
130
|
+
with open(icon_source, 'rb') as src:
|
|
131
|
+
icon_data = src.read()
|
|
132
|
+
|
|
133
|
+
with open(icon_dest, 'wb') as dst:
|
|
134
|
+
dst.write(icon_data)
|
|
135
|
+
except Exception:
|
|
136
|
+
icon_dest = "utilities-terminal" # Fallback to system icon
|
|
137
|
+
|
|
138
|
+
# Create .desktop file
|
|
139
|
+
desktop_content = f"""[Desktop Entry]
|
|
140
|
+
Name=SoulEyez
|
|
141
|
+
Comment=AI-Powered Penetration Testing Platform
|
|
142
|
+
Exec=souleyez interactive
|
|
143
|
+
Icon={icon_dest}
|
|
144
|
+
Terminal=true
|
|
145
|
+
Type=Application
|
|
146
|
+
Categories=Security;System;Network;
|
|
147
|
+
Keywords=pentest;security;hacking;nmap;metasploit;
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
desktop_file.write_text(desktop_content)
|
|
151
|
+
|
|
152
|
+
# Update desktop database (optional)
|
|
153
|
+
try:
|
|
154
|
+
subprocess.run(['update-desktop-database', str(applications_dir)],
|
|
155
|
+
capture_output=True, check=False, timeout=5)
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
click.echo(f" {click.style('✓', fg='green')} Desktop shortcut: Added to Applications menu")
|
|
160
|
+
|
|
161
|
+
except Exception:
|
|
162
|
+
# Silently ignore errors - desktop shortcut is nice-to-have
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _run_tool_installs(
|
|
167
|
+
missing_tools: List[Dict],
|
|
168
|
+
wrong_version_tools: List[Dict],
|
|
169
|
+
distro: str
|
|
170
|
+
) -> bool:
|
|
171
|
+
"""
|
|
172
|
+
Prompt user and run install/upgrade commands.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
missing_tools: List of missing tool dicts with 'name', 'install', 'tool_info'
|
|
176
|
+
wrong_version_tools: List of wrong version tool dicts
|
|
177
|
+
distro: Detected distribution
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if any installs were run
|
|
181
|
+
"""
|
|
182
|
+
from souleyez.utils.tool_checker import EXTERNAL_TOOLS, get_upgrade_command
|
|
183
|
+
|
|
184
|
+
all_installs = []
|
|
185
|
+
|
|
186
|
+
# Add wrong version tools (upgrades)
|
|
187
|
+
for tool in wrong_version_tools:
|
|
188
|
+
# Try to get upgrade command, fall back to install command
|
|
189
|
+
tool_info = tool.get('tool_info', {})
|
|
190
|
+
upgrade_cmd = get_upgrade_command(tool_info, distro) if tool_info else None
|
|
191
|
+
install_cmd = upgrade_cmd or tool.get('install', '')
|
|
192
|
+
|
|
193
|
+
all_installs.append({
|
|
194
|
+
'name': tool['name'],
|
|
195
|
+
'cmd': install_cmd,
|
|
196
|
+
'action': 'upgrade',
|
|
197
|
+
'tool_info': tool_info
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
# Add missing tools (installs)
|
|
201
|
+
for tool in missing_tools:
|
|
202
|
+
all_installs.append({
|
|
203
|
+
'name': tool['name'],
|
|
204
|
+
'cmd': tool['install'],
|
|
205
|
+
'action': 'install',
|
|
206
|
+
'tool_info': tool.get('tool_info', {})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
if not all_installs:
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
# Check disk space
|
|
213
|
+
if not _check_disk_space(500):
|
|
214
|
+
click.echo()
|
|
215
|
+
click.echo(f" {click.style('!', fg='yellow')} Low disk space (<500MB free). Installs may fail.")
|
|
216
|
+
if not click.confirm(" Continue anyway?", default=False):
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# Show what will be installed/upgraded
|
|
220
|
+
click.echo()
|
|
221
|
+
click.echo(f" {len(all_installs)} tool(s) to install/upgrade:")
|
|
222
|
+
for item in all_installs:
|
|
223
|
+
action_color = 'cyan' if item['action'] == 'upgrade' else 'green'
|
|
224
|
+
click.echo(f" - {item['name']} ({click.style(item['action'], fg=action_color)})")
|
|
225
|
+
|
|
226
|
+
click.echo()
|
|
227
|
+
if not click.confirm(" Install/upgrade now?", default=False):
|
|
228
|
+
click.echo(" Skipped. You can run 'souleyez setup' later to install tools.")
|
|
229
|
+
return False
|
|
230
|
+
|
|
231
|
+
# Request sudo upfront
|
|
232
|
+
click.echo()
|
|
233
|
+
click.echo(" Requesting sudo access...")
|
|
234
|
+
result = subprocess.run(['sudo', '-v'], check=False)
|
|
235
|
+
if result.returncode != 0:
|
|
236
|
+
click.echo(f" {click.style('x', fg='red')} Could not obtain sudo. Aborting installs.")
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
# Update apt cache first (for apt-based installs)
|
|
240
|
+
has_apt_installs = any('apt' in item['cmd'] for item in all_installs)
|
|
241
|
+
if has_apt_installs:
|
|
242
|
+
click.echo()
|
|
243
|
+
click.echo(" Updating package lists...")
|
|
244
|
+
result = subprocess.run(['sudo', 'apt', 'update'], capture_output=True)
|
|
245
|
+
if result.returncode != 0:
|
|
246
|
+
click.echo(f" {click.style('!', fg='yellow')} apt update had issues, continuing anyway...")
|
|
247
|
+
|
|
248
|
+
# Track results for summary
|
|
249
|
+
results = []
|
|
250
|
+
|
|
251
|
+
# Run each install command
|
|
252
|
+
for item in all_installs:
|
|
253
|
+
click.echo()
|
|
254
|
+
click.echo(f" {click.style(item['action'].capitalize() + 'ing', fg='cyan')} {item['name']}...")
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Run install command
|
|
258
|
+
proc = subprocess.run(
|
|
259
|
+
item['cmd'],
|
|
260
|
+
shell=True, # nosec B602 - commands from trusted EXTERNAL_TOOLS
|
|
261
|
+
timeout=600 # 10 minute timeout per tool
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if proc.returncode == 0:
|
|
265
|
+
click.echo(f" {click.style('✓', fg='green')} {item['name']} {item['action']} complete")
|
|
266
|
+
results.append({'name': item['name'], 'success': True, 'tool_info': item['tool_info']})
|
|
267
|
+
else:
|
|
268
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} failed (exit {proc.returncode})")
|
|
269
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
270
|
+
|
|
271
|
+
except subprocess.TimeoutExpired:
|
|
272
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} timed out")
|
|
273
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
274
|
+
except Exception as e:
|
|
275
|
+
click.echo(f" {click.style('✗', fg='red')} {item['name']} error: {e}")
|
|
276
|
+
results.append({'name': item['name'], 'success': False, 'tool_info': item['tool_info']})
|
|
277
|
+
|
|
278
|
+
# Configure sudoers for privileged tools that installed successfully
|
|
279
|
+
click.echo()
|
|
280
|
+
click.echo(" Configuring permissions for privileged tools...")
|
|
281
|
+
|
|
282
|
+
sudoers_configured = False
|
|
283
|
+
for res in results:
|
|
284
|
+
if not res['success']:
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
tool_info = res['tool_info']
|
|
288
|
+
if not tool_info.get('needs_sudo'):
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
sudoers_configured = True
|
|
292
|
+
# Find the actual binary path
|
|
293
|
+
command = tool_info.get('command', res['name'])
|
|
294
|
+
tool_path = shutil.which(command)
|
|
295
|
+
|
|
296
|
+
# Check alt_commands if primary not found
|
|
297
|
+
if not tool_path and tool_info.get('alt_commands'):
|
|
298
|
+
for alt in tool_info['alt_commands']:
|
|
299
|
+
tool_path = shutil.which(alt)
|
|
300
|
+
if tool_path:
|
|
301
|
+
break
|
|
302
|
+
|
|
303
|
+
if tool_path:
|
|
304
|
+
if _configure_sudoers(res['name'], tool_path):
|
|
305
|
+
click.echo(f" {click.style('✓', fg='green')} {res['name']} configured for passwordless sudo")
|
|
306
|
+
else:
|
|
307
|
+
click.echo(f" {click.style('!', fg='yellow')} {res['name']} sudoers config failed")
|
|
308
|
+
else:
|
|
309
|
+
click.echo(f" {click.style('!', fg='yellow')} {res['name']} binary not found, skipping sudoers")
|
|
310
|
+
|
|
311
|
+
if not sudoers_configured:
|
|
312
|
+
click.echo(" No privileged tools needed configuration.")
|
|
313
|
+
|
|
314
|
+
# Re-verify installed tools
|
|
315
|
+
click.echo()
|
|
316
|
+
click.echo(" Verifying installations...")
|
|
317
|
+
|
|
318
|
+
success_count = 0
|
|
319
|
+
fail_count = 0
|
|
320
|
+
|
|
321
|
+
for res in results:
|
|
322
|
+
tool_info = res['tool_info']
|
|
323
|
+
command = tool_info.get('command', res['name'])
|
|
324
|
+
alt_commands = tool_info.get('alt_commands')
|
|
325
|
+
|
|
326
|
+
# Check if tool is now available
|
|
327
|
+
found = shutil.which(command) is not None
|
|
328
|
+
if not found and alt_commands:
|
|
329
|
+
for alt in alt_commands:
|
|
330
|
+
if shutil.which(alt):
|
|
331
|
+
found = True
|
|
332
|
+
break
|
|
333
|
+
|
|
334
|
+
if found:
|
|
335
|
+
click.echo(f" {click.style('✓', fg='green')} {res['name']} verified")
|
|
336
|
+
success_count += 1
|
|
337
|
+
else:
|
|
338
|
+
click.echo(f" {click.style('✗', fg='red')} {res['name']} not found after install")
|
|
339
|
+
fail_count += 1
|
|
340
|
+
|
|
341
|
+
# Summary
|
|
342
|
+
click.echo()
|
|
343
|
+
if fail_count == 0:
|
|
344
|
+
click.echo(f" {click.style('✓', fg='green')} All {success_count} tool(s) installed successfully!")
|
|
345
|
+
else:
|
|
346
|
+
click.echo(f" {click.style('!', fg='yellow')} {success_count} succeeded, {fail_count} failed")
|
|
347
|
+
click.echo(" Failed tools may need manual installation.")
|
|
348
|
+
|
|
349
|
+
return True
|
|
350
|
+
|
|
29
351
|
|
|
30
352
|
def _show_wizard_banner():
|
|
31
353
|
"""Display the SoulEyez ASCII banner for wizard steps."""
|
|
32
354
|
click.echo()
|
|
33
|
-
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True))
|
|
34
|
-
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True))
|
|
35
|
-
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
36
|
-
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True))
|
|
37
|
-
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True))
|
|
38
|
-
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True))
|
|
355
|
+
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▄██▄", fg='bright_blue', bold=True))
|
|
356
|
+
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True) + click.style(" ▄█▀ ▀█▄", fg='bright_blue', bold=True))
|
|
357
|
+
click.echo(click.style(" ███████╗██║ ██║██║ ██║██║ █████╗ ╚████╔╝ █████╗ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ◉ █", fg='bright_blue', bold=True))
|
|
358
|
+
click.echo(click.style(" ╚════██║██║ ██║██║ ██║██║ ██╔══╝ ╚██╔╝ ██╔══╝ ███╔╝ ", fg='bright_cyan', bold=True) + click.style(" █ ═══ █", fg='bright_blue', bold=True))
|
|
359
|
+
click.echo(click.style(" ███████║╚██████╔╝╚██████╔╝███████╗███████╗ ██║ ███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▀█▄ ▄█▀", fg='bright_blue', bold=True))
|
|
360
|
+
click.echo(click.style(" ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝ ╚═╝ ╚══════╝╚══════╝", fg='bright_cyan', bold=True) + click.style(" ▀██▀", fg='bright_blue', bold=True))
|
|
39
361
|
click.echo()
|
|
40
362
|
click.echo(click.style(" Created by CyberSoul SecurITy", fg='bright_blue'))
|
|
41
363
|
click.echo()
|
|
@@ -44,7 +366,7 @@ def _show_wizard_banner():
|
|
|
44
366
|
def run_setup_wizard() -> bool:
|
|
45
367
|
"""
|
|
46
368
|
Run the setup wizard for new users.
|
|
47
|
-
|
|
369
|
+
|
|
48
370
|
Returns:
|
|
49
371
|
bool: True if wizard completed, False if skipped/cancelled
|
|
50
372
|
"""
|
|
@@ -53,20 +375,20 @@ def run_setup_wizard() -> bool:
|
|
|
53
375
|
from souleyez.auth import get_current_user, Tier
|
|
54
376
|
user = get_current_user()
|
|
55
377
|
is_pro = user and user.tier == Tier.PRO
|
|
56
|
-
|
|
378
|
+
|
|
57
379
|
# Step 1: Welcome
|
|
58
380
|
if not _wizard_welcome(is_pro):
|
|
59
381
|
mark_wizard_completed() # Mark as completed even if skipped
|
|
60
382
|
return False
|
|
61
|
-
|
|
383
|
+
|
|
62
384
|
# Step 2: Encryption Setup
|
|
63
385
|
encryption_enabled = _wizard_encryption_setup()
|
|
64
|
-
|
|
386
|
+
|
|
65
387
|
# Step 3: Create First Engagement
|
|
66
388
|
engagement_info = _wizard_create_engagement()
|
|
67
389
|
if not engagement_info:
|
|
68
390
|
return False
|
|
69
|
-
|
|
391
|
+
|
|
70
392
|
# Step 4: Tool Check
|
|
71
393
|
tool_status = _wizard_tool_check()
|
|
72
394
|
|
|
@@ -78,7 +400,7 @@ def run_setup_wizard() -> bool:
|
|
|
78
400
|
automation_prefs = _wizard_automation_prefs()
|
|
79
401
|
else:
|
|
80
402
|
automation_prefs = {'enabled': False, 'mode': None}
|
|
81
|
-
|
|
403
|
+
|
|
82
404
|
# Step 7: Deliverable Templates (Pro only)
|
|
83
405
|
if is_pro:
|
|
84
406
|
templates = _wizard_deliverables(engagement_info.get('type'))
|
|
@@ -95,10 +417,10 @@ def run_setup_wizard() -> bool:
|
|
|
95
417
|
templates,
|
|
96
418
|
is_pro
|
|
97
419
|
)
|
|
98
|
-
|
|
420
|
+
|
|
99
421
|
mark_wizard_completed()
|
|
100
422
|
return True
|
|
101
|
-
|
|
423
|
+
|
|
102
424
|
except (KeyboardInterrupt, click.Abort):
|
|
103
425
|
click.echo(click.style("\n\n Setup wizard cancelled.", fg='yellow'))
|
|
104
426
|
mark_wizard_completed() # Don't show wizard again
|
|
@@ -110,13 +432,13 @@ def _wizard_welcome(is_pro: bool = False) -> bool:
|
|
|
110
432
|
"""Show welcome screen with ASCII banner."""
|
|
111
433
|
DesignSystem.clear_screen()
|
|
112
434
|
width = DesignSystem.get_terminal_width()
|
|
113
|
-
|
|
435
|
+
|
|
114
436
|
# Header
|
|
115
437
|
click.echo("\n┌" + "─" * (width - 2) + "┐")
|
|
116
438
|
click.echo("│" + click.style(" WELCOME TO SOULEYEZ - SETUP WIZARD ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
117
439
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
118
440
|
click.echo()
|
|
119
|
-
|
|
441
|
+
|
|
120
442
|
# ASCII Art Banner - SOULEYEZ with all-seeing eye on the right
|
|
121
443
|
click.echo(click.style(" ███████╗ ██████╗ ██╗ ██╗██╗ ███████╗██╗ ██╗███████╗███████╗", fg='bright_cyan', bold=True) + click.style(" ▄██▄", fg='bright_blue', bold=True))
|
|
122
444
|
click.echo(click.style(" ██╔════╝██╔═══██╗██║ ██║██║ ██╔════╝╚██╗ ██╔╝██╔════╝╚══███╔╝", fg='bright_cyan', bold=True) + click.style(" ▄█▀ ▀█▄", fg='bright_blue', bold=True))
|
|
@@ -166,15 +488,15 @@ def _wizard_encryption_setup() -> bool:
|
|
|
166
488
|
click.echo("│" + click.style(" STEP 2: ENCRYPTION SETUP ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
167
489
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
168
490
|
click.echo()
|
|
169
|
-
|
|
491
|
+
|
|
170
492
|
crypto = CryptoManager()
|
|
171
|
-
|
|
493
|
+
|
|
172
494
|
if crypto.is_encryption_enabled():
|
|
173
495
|
click.echo(" " + click.style("✓ Encryption already configured", fg='green'))
|
|
174
496
|
click.echo()
|
|
175
497
|
click.pause(" Press any key to continue...")
|
|
176
498
|
return True
|
|
177
|
-
|
|
499
|
+
|
|
178
500
|
click.echo(" SoulEyez encrypts all credentials (passwords, API keys, tokens)")
|
|
179
501
|
click.echo(" with a master password. This is " + click.style("required", fg='green', bold=True) + " for security.")
|
|
180
502
|
click.echo()
|
|
@@ -204,46 +526,46 @@ def _wizard_encryption_setup() -> bool:
|
|
|
204
526
|
click.echo(" • At least one number")
|
|
205
527
|
click.echo(" • At least one special character (!@#$%^&*)")
|
|
206
528
|
click.echo()
|
|
207
|
-
click.echo(" " + click.style("⚠️ If you lose this password, encrypted credentials cannot be recovered!",
|
|
529
|
+
click.echo(" " + click.style("⚠️ If you lose this password, encrypted credentials cannot be recovered!",
|
|
208
530
|
fg='yellow', bold=True))
|
|
209
531
|
click.echo()
|
|
210
|
-
|
|
532
|
+
|
|
211
533
|
import re
|
|
212
534
|
password_set = False
|
|
213
535
|
while not password_set:
|
|
214
536
|
password = getpass.getpass(" Enter master password: ")
|
|
215
|
-
|
|
537
|
+
|
|
216
538
|
# Validate password strength
|
|
217
539
|
if len(password) < 12:
|
|
218
540
|
click.echo(click.style(" ✗ Password must be at least 12 characters.", fg='red'))
|
|
219
541
|
continue
|
|
220
|
-
|
|
542
|
+
|
|
221
543
|
if not re.search(r'[a-z]', password):
|
|
222
544
|
click.echo(click.style(" ✗ Password must contain at least one lowercase letter.", fg='red'))
|
|
223
545
|
continue
|
|
224
|
-
|
|
546
|
+
|
|
225
547
|
if not re.search(r'[A-Z]', password):
|
|
226
548
|
click.echo(click.style(" ✗ Password must contain at least one uppercase letter.", fg='red'))
|
|
227
549
|
continue
|
|
228
|
-
|
|
550
|
+
|
|
229
551
|
if not re.search(r'\d', password):
|
|
230
552
|
click.echo(click.style(" ✗ Password must contain at least one number.", fg='red'))
|
|
231
553
|
continue
|
|
232
|
-
|
|
554
|
+
|
|
233
555
|
if not re.search(r'[!@#$%^&*()_+\-=\[\]{};:\'",.<>?/\\|`~]', password):
|
|
234
556
|
click.echo(click.style(" ✗ Password must contain at least one special character.", fg='red'))
|
|
235
557
|
continue
|
|
236
|
-
|
|
558
|
+
|
|
237
559
|
password_confirm = getpass.getpass(" Confirm master password: ")
|
|
238
560
|
if password != password_confirm:
|
|
239
561
|
click.echo(click.style(" ✗ Passwords don't match!", fg='red'))
|
|
240
562
|
continue
|
|
241
|
-
|
|
563
|
+
|
|
242
564
|
password_set = True
|
|
243
|
-
|
|
565
|
+
|
|
244
566
|
click.echo()
|
|
245
567
|
click.echo(" Enabling encryption...")
|
|
246
|
-
|
|
568
|
+
|
|
247
569
|
try:
|
|
248
570
|
if crypto.enable_encryption(password):
|
|
249
571
|
click.echo(" " + click.style("✓ Encryption enabled!", fg='green'))
|
|
@@ -255,7 +577,7 @@ def _wizard_encryption_setup() -> bool:
|
|
|
255
577
|
click.echo(" " + click.style(f"✗ Error: {e}", fg='red'))
|
|
256
578
|
click.pause(" Press any key to continue...")
|
|
257
579
|
return False
|
|
258
|
-
|
|
580
|
+
|
|
259
581
|
click.pause(" Press any key to continue...")
|
|
260
582
|
return True
|
|
261
583
|
|
|
@@ -293,9 +615,9 @@ def _wizard_create_engagement() -> dict:
|
|
|
293
615
|
click.echo()
|
|
294
616
|
click.echo(" " + click.style("NOTE:", fg='yellow') + " Type affects default automation and scan aggressiveness")
|
|
295
617
|
click.echo()
|
|
296
|
-
|
|
618
|
+
|
|
297
619
|
type_choice = click.prompt(" Select option", type=click.IntRange(1, 5), default=1, show_default=False)
|
|
298
|
-
|
|
620
|
+
|
|
299
621
|
engagement_types = {
|
|
300
622
|
1: 'penetration_test',
|
|
301
623
|
2: 'bug_bounty',
|
|
@@ -303,7 +625,7 @@ def _wizard_create_engagement() -> dict:
|
|
|
303
625
|
4: 'red_team',
|
|
304
626
|
5: 'custom'
|
|
305
627
|
}
|
|
306
|
-
|
|
628
|
+
|
|
307
629
|
engagement_type = engagement_types[type_choice]
|
|
308
630
|
|
|
309
631
|
# Create engagement and set it as active
|
|
@@ -338,7 +660,8 @@ def _wizard_create_engagement() -> dict:
|
|
|
338
660
|
def _wizard_tool_check() -> dict:
|
|
339
661
|
"""Check installed tools using the centralized tool_checker module."""
|
|
340
662
|
from souleyez.utils.tool_checker import (
|
|
341
|
-
check_tool_version, EXTERNAL_TOOLS, get_install_command, detect_distro
|
|
663
|
+
check_tool_version, EXTERNAL_TOOLS, get_install_command, detect_distro,
|
|
664
|
+
get_tool_version, get_upgrade_command
|
|
342
665
|
)
|
|
343
666
|
|
|
344
667
|
DesignSystem.clear_screen()
|
|
@@ -373,6 +696,7 @@ def _wizard_tool_check() -> dict:
|
|
|
373
696
|
total = len(wizard_tools)
|
|
374
697
|
tool_status = {}
|
|
375
698
|
wrong_version_tools = []
|
|
699
|
+
missing_tools = []
|
|
376
700
|
|
|
377
701
|
for display_name, (category, tool_key) in wizard_tools.items():
|
|
378
702
|
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
@@ -390,7 +714,6 @@ def _wizard_tool_check() -> dict:
|
|
|
390
714
|
version_str = version_status['version']
|
|
391
715
|
if not version_str:
|
|
392
716
|
# Try to get version using the actual found command
|
|
393
|
-
from souleyez.utils.tool_checker import get_tool_version
|
|
394
717
|
version_str = get_tool_version(actual_cmd)
|
|
395
718
|
|
|
396
719
|
# Check if version is OK
|
|
@@ -410,8 +733,9 @@ def _wizard_tool_check() -> dict:
|
|
|
410
733
|
'name': display_name,
|
|
411
734
|
'installed': ver_display,
|
|
412
735
|
'required': min_ver,
|
|
413
|
-
'install': get_install_command(tool_info, distro),
|
|
414
|
-
'note': version_status.get('version_note')
|
|
736
|
+
'install': get_upgrade_command(tool_info, distro) or get_install_command(tool_info, distro),
|
|
737
|
+
'note': version_status.get('version_note'),
|
|
738
|
+
'tool_info': tool_info
|
|
415
739
|
})
|
|
416
740
|
found += 1 # Still counts as found, just needs upgrade
|
|
417
741
|
else:
|
|
@@ -423,6 +747,11 @@ def _wizard_tool_check() -> dict:
|
|
|
423
747
|
else:
|
|
424
748
|
click.echo(f" {click.style('✗', fg='red')} {display_name:<15} NOT FOUND")
|
|
425
749
|
tool_status[display_name] = {'found': False, 'path': None}
|
|
750
|
+
missing_tools.append({
|
|
751
|
+
'name': display_name,
|
|
752
|
+
'install': get_install_command(tool_info, distro),
|
|
753
|
+
'tool_info': tool_info
|
|
754
|
+
})
|
|
426
755
|
|
|
427
756
|
click.echo()
|
|
428
757
|
click.echo(f" Found {found}/{total} recommended tools")
|
|
@@ -435,17 +764,34 @@ def _wizard_tool_check() -> dict:
|
|
|
435
764
|
click.echo(f" - {tool['name']}: installed v{tool['installed']}, needs v{tool['required']}+")
|
|
436
765
|
if tool.get('note'):
|
|
437
766
|
click.echo(f" {click.style(tool['note'], fg='bright_black')}")
|
|
767
|
+
|
|
768
|
+
# Offer to install/upgrade tools
|
|
769
|
+
if missing_tools or wrong_version_tools:
|
|
438
770
|
click.echo()
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
771
|
+
# Run the install flow (will prompt user)
|
|
772
|
+
if _run_tool_installs(missing_tools, wrong_version_tools, distro):
|
|
773
|
+
# Re-check tools after install
|
|
774
|
+
click.echo()
|
|
775
|
+
click.echo(" Re-checking tool availability...")
|
|
776
|
+
click.echo()
|
|
442
777
|
|
|
443
|
-
|
|
778
|
+
found = 0
|
|
779
|
+
wrong_version_tools = []
|
|
780
|
+
for display_name, (category, tool_key) in wizard_tools.items():
|
|
781
|
+
tool_info = EXTERNAL_TOOLS[category][tool_key]
|
|
782
|
+
version_status = check_tool_version(tool_info)
|
|
783
|
+
|
|
784
|
+
if version_status['installed'] and not version_status['needs_upgrade']:
|
|
785
|
+
tool_status[display_name] = {'found': True, 'path': shutil.which(tool_info['command'])}
|
|
786
|
+
found += 1
|
|
787
|
+
elif version_status['installed'] and version_status['needs_upgrade']:
|
|
788
|
+
tool_status[display_name] = {'found': True, 'needs_upgrade': True}
|
|
789
|
+
wrong_version_tools.append({'name': display_name})
|
|
790
|
+
found += 1
|
|
791
|
+
else:
|
|
792
|
+
tool_status[display_name] = {'found': False, 'path': None}
|
|
444
793
|
|
|
445
|
-
|
|
446
|
-
click.echo(" " + click.style("TIP:", fg='yellow', bold=True) +
|
|
447
|
-
" You can install missing tools later. SoulEyez will work")
|
|
448
|
-
click.echo(" with the tools you have available.")
|
|
794
|
+
click.echo(f" Now have {found}/{total} tools available")
|
|
449
795
|
|
|
450
796
|
click.echo()
|
|
451
797
|
click.pause(" Press any key to continue...")
|
|
@@ -556,14 +902,16 @@ def _install_ollama() -> bool:
|
|
|
556
902
|
|
|
557
903
|
click.echo()
|
|
558
904
|
click.echo(" Installing Ollama...")
|
|
559
|
-
click.echo(" " + click.style("(This may take a minute)", fg='bright_black'))
|
|
905
|
+
click.echo(" " + click.style("(This may take a minute - downloading ~100MB)", fg='bright_black'))
|
|
560
906
|
click.echo()
|
|
561
907
|
|
|
562
908
|
try:
|
|
563
|
-
# Run the official Ollama install script
|
|
909
|
+
# Run the official Ollama install script with timeout
|
|
910
|
+
# Add curl timeouts to fail faster on network issues
|
|
564
911
|
result = subprocess.run(
|
|
565
|
-
['bash', '-c', 'curl -fsSL https://ollama.ai/install.sh | sh'],
|
|
566
|
-
check=False
|
|
912
|
+
['bash', '-c', 'curl --connect-timeout 30 --max-time 300 -fsSL https://ollama.ai/install.sh | sh'],
|
|
913
|
+
check=False,
|
|
914
|
+
timeout=360 # 6 minute total timeout
|
|
567
915
|
)
|
|
568
916
|
|
|
569
917
|
if result.returncode == 0:
|
|
@@ -575,10 +923,25 @@ def _install_ollama() -> bool:
|
|
|
575
923
|
else:
|
|
576
924
|
click.echo()
|
|
577
925
|
click.echo(" " + click.style("✗ Installation failed", fg='red'))
|
|
578
|
-
click.echo("
|
|
926
|
+
click.echo(" " + click.style("This is usually a network issue (slow connection or timeout).", fg='yellow'))
|
|
927
|
+
click.echo()
|
|
928
|
+
click.echo(" To install manually:")
|
|
929
|
+
click.echo(" curl -fsSL https://ollama.ai/install.sh | sh")
|
|
930
|
+
click.echo()
|
|
931
|
+
click.echo(" Or visit: https://ollama.ai")
|
|
579
932
|
click.pause(" Press any key to continue...")
|
|
580
933
|
return False
|
|
581
934
|
|
|
935
|
+
except subprocess.TimeoutExpired:
|
|
936
|
+
click.echo()
|
|
937
|
+
click.echo(" " + click.style("✗ Installation timed out", fg='red'))
|
|
938
|
+
click.echo(" " + click.style("The download took too long. Check your internet connection.", fg='yellow'))
|
|
939
|
+
click.echo()
|
|
940
|
+
click.echo(" To install manually:")
|
|
941
|
+
click.echo(" curl -fsSL https://ollama.ai/install.sh | sh")
|
|
942
|
+
click.pause(" Press any key to continue...")
|
|
943
|
+
return False
|
|
944
|
+
|
|
582
945
|
except Exception as e:
|
|
583
946
|
click.echo()
|
|
584
947
|
click.echo(click.style(f" ✗ Error: {e}", fg='red'))
|
|
@@ -722,7 +1085,7 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
722
1085
|
DesignSystem.clear_screen()
|
|
723
1086
|
_show_wizard_banner()
|
|
724
1087
|
width = 60
|
|
725
|
-
|
|
1088
|
+
|
|
726
1089
|
click.echo("┌" + "─" * (width - 2) + "┐")
|
|
727
1090
|
click.echo("│" + click.style(" STEP 7: REPORT TEMPLATES ".center(width - 2), bold=True, fg='cyan') + "│")
|
|
728
1091
|
click.echo("└" + "─" * (width - 2) + "┘")
|
|
@@ -732,7 +1095,7 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
732
1095
|
click.echo()
|
|
733
1096
|
click.echo(" " + click.style("NOTE:", fg='yellow') + " Templates help generate professional reports from your findings")
|
|
734
1097
|
click.echo()
|
|
735
|
-
|
|
1098
|
+
|
|
736
1099
|
# Default templates based on engagement type
|
|
737
1100
|
type_templates = {
|
|
738
1101
|
'penetration_test': ['Executive Summary', 'Technical Findings Report', 'Vulnerability Details'],
|
|
@@ -741,9 +1104,9 @@ def _wizard_deliverables(engagement_type: str) -> list:
|
|
|
741
1104
|
'red_team': ['Attack Narrative', 'Remediation Roadmap'],
|
|
742
1105
|
'custom': ['Technical Findings Report']
|
|
743
1106
|
}
|
|
744
|
-
|
|
1107
|
+
|
|
745
1108
|
recommended = type_templates.get(engagement_type, ['Technical Findings Report'])
|
|
746
|
-
|
|
1109
|
+
|
|
747
1110
|
click.echo(f" Recommended for {engagement_type.replace('_', ' ').title()}:")
|
|
748
1111
|
for template in recommended:
|
|
749
1112
|
click.echo(f" {click.style('✓', fg='green')} {template}")
|
|
@@ -785,7 +1148,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
785
1148
|
click.echo(f" {click.style('✓', fg='green')} AI Features: Enabled (Ollama)")
|
|
786
1149
|
else:
|
|
787
1150
|
click.echo(f" {click.style('○', fg='bright_black')} AI Features: Not configured")
|
|
788
|
-
|
|
1151
|
+
|
|
789
1152
|
# Show tier status
|
|
790
1153
|
if is_pro:
|
|
791
1154
|
click.echo(f" {click.style('💎', fg='magenta')} License: PRO")
|
|
@@ -794,7 +1157,7 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
794
1157
|
click.echo(f" {click.style('✓', fg='green')} Auto-Chain: ON ({auto_mode} mode)")
|
|
795
1158
|
else:
|
|
796
1159
|
click.echo(f" {click.style('✓', fg='green')} Auto-Chain: OFF")
|
|
797
|
-
|
|
1160
|
+
|
|
798
1161
|
if templates:
|
|
799
1162
|
click.echo(f" {click.style('✓', fg='green')} Templates: {len(templates)} selected")
|
|
800
1163
|
else:
|
|
@@ -806,11 +1169,14 @@ def _wizard_summary(encryption_enabled, engagement_info, tool_status, ai_enabled
|
|
|
806
1169
|
click.echo(" • MSF Integration - Advanced attack chains")
|
|
807
1170
|
click.echo(" • Reports - Professional deliverables")
|
|
808
1171
|
click.echo(f" {click.style('→ cybersoulsecurity.com/upgrade', fg='cyan')}")
|
|
809
|
-
|
|
1172
|
+
|
|
810
1173
|
click.echo()
|
|
811
1174
|
click.echo(" " + click.style("You're ready to start!", bold=True, fg='cyan'))
|
|
812
1175
|
click.echo()
|
|
813
1176
|
|
|
1177
|
+
# Install desktop shortcut automatically
|
|
1178
|
+
_install_desktop_shortcut()
|
|
1179
|
+
|
|
814
1180
|
# Prompt for interactive tutorial
|
|
815
1181
|
click.echo(" ┌" + "─" * 56 + "┐")
|
|
816
1182
|
click.echo(" │" + click.style(" Would you like to run the interactive tutorial?", fg='cyan').center(65) + "│")
|