toolbelt/utils.py
rpriven fd0659d70c
Toolbelt v2.0 - Complete refactor with interactive menus and profiles
Major rewrite from single-file script to modular architecture with comprehensive features:

## New Features
- Interactive 3-level menu system (Main → Categories → Tools)
- Pre-built profiles: Bug Bounty, CTF, Web App, Network, Full Pentest
- Distro detection with appropriate tool sets (Kali, Debian, Ubuntu)
- NO root requirement - runs as user, uses sudo selectively
- Comprehensive logging (console + ~/toolbelt-install.log)
- Fresh CLI tool integration and detection
- Smart tool detection (skips already-installed tools)

## Architecture Changes
- Modular design: utils.py, config.py, installer.py, toolbelt.py
- utils.py: Distro detection, logging setup, helper functions
- config.py: Tool definitions, profiles, category metadata
- installer.py: Installation logic for all tool categories
- toolbelt.py: Interactive menu system and main flow

## Improvements
- Fixed $HOME path resolution bug (no more /root issues)
- Added comprehensive error handling and reporting
- Category-based tool organization (APT, Go, /opt, Python, Docker, Scripts)
- Parallel Go tool installation with ThreadPoolExecutor
- Shell alias setup for Docker tools

## Documentation
- Complete README rewrite with usage examples
- Architecture diagrams and file structure
- Integration guide for fresh ecosystem
- Version history and changelog

## Archived Files
- toolbelt.sh → toolbelt.sh.old (legacy bash version)
- toolbelt.py → toolbelt_old.py (original Python v1.0)

Part of the Djedi security tooling ecosystem integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 23:17:41 -06:00

354 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Djedi Toolbelt - Utility Functions
Provides distro detection, logging, and helper functions
"""
import os
import sys
import subprocess
import logging
from pathlib import Path
from datetime import datetime
from typing import Optional, List, Tuple
# ============================================================================
# Distro Detection
# ============================================================================
def detect_distro() -> Tuple[str, str]:
"""
Detect the Linux distribution
Returns:
Tuple of (distro_name, distro_type)
distro_type is one of: 'kali', 'debian', 'ubuntu', 'unknown'
"""
distro_name = "Unknown"
distro_type = "unknown"
if not os.path.exists('/etc/os-release'):
return (distro_name, distro_type)
try:
with open('/etc/os-release', 'r') as f:
content = f.read()
# Extract PRETTY_NAME
for line in content.split('\n'):
if line.startswith('PRETTY_NAME='):
distro_name = line.split('=')[1].strip('"')
break
# Determine distro type
content_lower = content.lower()
if 'kali' in content_lower:
distro_type = 'kali'
elif 'debian' in content_lower:
distro_type = 'debian'
elif 'ubuntu' in content_lower:
distro_type = 'ubuntu'
except Exception as e:
logging.error(f"Error detecting distro: {e}")
return (distro_name, distro_type)
def is_kali() -> bool:
"""Check if running on Kali Linux"""
_, distro_type = detect_distro()
return distro_type == 'kali'
def is_debian_based() -> bool:
"""Check if running on Debian-based system (Debian, Ubuntu, Kali)"""
_, distro_type = detect_distro()
return distro_type in ['kali', 'debian', 'ubuntu']
# ============================================================================
# Logging Setup
# ============================================================================
def setup_logging(log_file: Optional[str] = None) -> logging.Logger:
"""
Setup logging with both file and console output
Args:
log_file: Optional path to log file. Defaults to ~/toolbelt-install.log
Returns:
Configured logger instance
"""
if log_file is None:
log_file = os.path.expanduser("~/toolbelt-install.log")
# Create logger
logger = logging.getLogger('toolbelt')
logger.setLevel(logging.DEBUG)
# Clear existing handlers
logger.handlers.clear()
# File handler (DEBUG level)
file_handler = logging.FileHandler(log_file, mode='a')
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(file_formatter)
# Console handler (INFO level)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter('[%(levelname)s] %(message)s')
console_handler.setFormatter(console_formatter)
# Add handlers
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# Log session start
logger.info("=" * 60)
logger.info(f"Toolbelt session started: {datetime.now()}")
logger.info("=" * 60)
return logger
# ============================================================================
# System Checks
# ============================================================================
def is_root() -> bool:
"""Check if running as root"""
return os.geteuid() == 0
def check_root_discourage():
"""Warn user if running as root (we don't want root!)"""
if is_root():
print("\033[91m") # Red
print("=" * 60)
print("WARNING: Running as root is NOT recommended!")
print("=" * 60)
print("\033[0m") # Reset
print()
print("This script will use sudo for specific commands that need it.")
print("Running the entire script as root can cause issues:")
print(" • Scripts download to /root instead of your home directory")
print(" • Files owned by root instead of your user")
print(" • Potential security issues")
print()
response = input("Continue anyway? [y/N]: ").strip().lower()
if response != 'y':
print("Exiting. Please run without sudo/root.")
sys.exit(0)
def check_command_exists(command: str) -> bool:
"""
Check if a command exists in PATH
Args:
command: Command name to check
Returns:
True if command exists, False otherwise
"""
try:
result = subprocess.run(
['which', command],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
return result.returncode == 0
except Exception:
return False
def check_apt() -> bool:
"""Check if apt package manager is available"""
return check_command_exists('apt')
def check_sudo() -> bool:
"""Check if sudo is available"""
return check_command_exists('sudo')
# ============================================================================
# Helper Functions
# ============================================================================
def run_command(
cmd: List[str],
use_sudo: bool = False,
shell: bool = False,
check: bool = True,
logger: Optional[logging.Logger] = None
) -> subprocess.CompletedProcess:
"""
Run a command with optional sudo
Args:
cmd: Command and arguments as list
use_sudo: Prepend sudo if True
shell: Run as shell command if True
check: Raise exception on non-zero exit if True
logger: Optional logger instance
Returns:
CompletedProcess instance
"""
if use_sudo and not is_root():
if isinstance(cmd, list):
cmd = ['sudo'] + cmd
else:
cmd = f"sudo {cmd}"
if logger:
cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
logger.debug(f"Running: {cmd_str}")
try:
result = subprocess.run(
cmd,
shell=shell,
check=check,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return result
except subprocess.CalledProcessError as e:
if logger:
logger.error(f"Command failed: {e.cmd}")
logger.error(f"Exit code: {e.returncode}")
if e.stdout:
logger.error(f"Stdout: {e.stdout}")
if e.stderr:
logger.error(f"Stderr: {e.stderr}")
raise
def ensure_directory(path: str, use_sudo: bool = False) -> bool:
"""
Ensure directory exists, create if necessary
Args:
path: Directory path
use_sudo: Use sudo to create if True
Returns:
True if directory exists or was created successfully
"""
expanded_path = os.path.expanduser(path)
if os.path.isdir(expanded_path):
return True
try:
if use_sudo:
subprocess.run(['sudo', 'mkdir', '-p', expanded_path], check=True)
else:
os.makedirs(expanded_path, exist_ok=True)
return True
except Exception as e:
logging.error(f"Failed to create directory {expanded_path}: {e}")
return False
def get_home_dir() -> str:
"""
Get the actual user's home directory (not /root even if running as root)
Returns:
Path to user's home directory
"""
# If running as root, try to get the original user's home
if is_root():
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
return os.path.expanduser(f"~{sudo_user}")
# Otherwise return normal home
return os.path.expanduser("~")
def colorize(text: str, color: str) -> str:
"""
Colorize text for terminal output
Args:
text: Text to colorize
color: Color name (red, green, yellow, blue, magenta, cyan)
Returns:
Colorized text string
"""
colors = {
'red': '\033[91m',
'green': '\033[92m',
'yellow': '\033[93m',
'blue': '\033[94m',
'magenta': '\033[95m',
'cyan': '\033[96m',
'white': '\033[97m',
'reset': '\033[0m'
}
color_code = colors.get(color.lower(), colors['reset'])
return f"{color_code}{text}{colors['reset']}"
# ============================================================================
# Display Functions
# ============================================================================
def print_banner():
"""Print the toolbelt banner"""
banner = """
╔══════════════════════════════════════════════════════════╗
║ ║
║ 🔧 DJEDI TOOLBELT 🔧 ║
║ ║
║ Comprehensive Security Tool Installer ║
║ ║
╚══════════════════════════════════════════════════════════╝
"""
print(colorize(banner, 'cyan'))
print(colorize(" v2.0.0", 'magenta'))
print()
def print_section(title: str):
"""Print a section header"""
print()
print(colorize("=" * 60, 'cyan'))
print(colorize(f" {title}", 'white'))
print(colorize("=" * 60, 'cyan'))
print()
def print_success(message: str):
"""Print success message"""
print(colorize(f"{message}", 'green'))
def print_info(message: str):
"""Print info message"""
print(colorize(f"{message}", 'blue'))
def print_warning(message: str):
"""Print warning message"""
print(colorize(f"{message}", 'yellow'))
def print_error(message: str):
"""Print error message"""
print(colorize(f"{message}", 'red'))