mirror of
https://github.com/Z4nzu/hackingtool
synced 2026-05-23 17:08:25 +00:00
Phase 0: Add foundation files for v2.0.0 restructure
- constants.py: single source of truth for repo URLs, version (2.0.0), all paths via Path.home(), UI theme constants, PRIV_CMD auto-detection - os_detect.py: OSInfo dataclass, auto-detect OS/distro/package manager, CURRENT_OS singleton, per-OS install command maps - config.py: get_tools_dir(), load()/save() config.json, get_sudo_cmd() - tools/__init__.py, tools/others/__init__.py: make proper Python packages - IMPLEMENTATION.md: full 18-section restructuring plan (2350+ lines) - LOG.md: 13-phase progress tracker
This commit is contained in:
parent
7df27d8383
commit
6a32c8ac05
5 changed files with 236 additions and 0 deletions
43
config.py
Normal file
43
config.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from constants import USER_CONFIG_FILE, USER_TOOLS_DIR, DEFAULT_CONFIG
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load() -> dict[str, Any]:
|
||||
"""Load config from disk, merging with defaults for any missing keys."""
|
||||
if USER_CONFIG_FILE.exists():
|
||||
try:
|
||||
on_disk = json.loads(USER_CONFIG_FILE.read_text())
|
||||
return {**DEFAULT_CONFIG, **on_disk}
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
logger.warning("Config file unreadable (%s), using defaults.", exc)
|
||||
return dict(DEFAULT_CONFIG)
|
||||
|
||||
|
||||
def save(cfg: dict[str, Any]) -> None:
|
||||
"""Write config to disk, creating parent directories if needed."""
|
||||
USER_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
USER_CONFIG_FILE.write_text(json.dumps(cfg, indent=2, sort_keys=True))
|
||||
|
||||
|
||||
def get_tools_dir() -> Path:
|
||||
"""
|
||||
Return the directory where external tools are stored.
|
||||
Creates it if it does not exist.
|
||||
Always an absolute path — never relies on process CWD.
|
||||
"""
|
||||
cfg = load()
|
||||
tools_dir = Path(cfg.get("tools_dir", str(USER_TOOLS_DIR))).expanduser().resolve()
|
||||
tools_dir.mkdir(parents=True, exist_ok=True)
|
||||
return tools_dir
|
||||
|
||||
|
||||
def get_sudo_cmd() -> str:
|
||||
"""Return 'doas' if available, else 'sudo'. Never hardcode 'sudo'."""
|
||||
import shutil
|
||||
return "doas" if shutil.which("doas") else "sudo"
|
||||
65
constants.py
Normal file
65
constants.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
from pathlib import Path
|
||||
import platform
|
||||
import shutil as _shutil
|
||||
|
||||
# ── Repository ────────────────────────────────────────────────────────────────
|
||||
REPO_OWNER = "Z4nzu"
|
||||
REPO_NAME = "hackingtool"
|
||||
REPO_URL = f"https://github.com/{REPO_OWNER}/{REPO_NAME}.git"
|
||||
REPO_WEB_URL = f"https://github.com/{REPO_OWNER}/{REPO_NAME}"
|
||||
|
||||
# ── Versioning ────────────────────────────────────────────────────────────────
|
||||
VERSION = "2.0.0"
|
||||
VERSION_DISPLAY = f"v{VERSION}"
|
||||
|
||||
# ── Python requirement ────────────────────────────────────────────────────────
|
||||
MIN_PYTHON = (3, 10)
|
||||
|
||||
# ── User-scoped paths (cross-platform, always computed at runtime) ─────────────
|
||||
# NEVER hardcode /home/username — use Path.home() so it works for any user,
|
||||
# including root (/root), regular users (/home/alice), macOS (/Users/alice).
|
||||
USER_CONFIG_DIR = Path.home() / f".{REPO_NAME}"
|
||||
USER_TOOLS_DIR = USER_CONFIG_DIR / "tools"
|
||||
USER_CONFIG_FILE = USER_CONFIG_DIR / "config.json"
|
||||
USER_LOG_FILE = USER_CONFIG_DIR / f"{REPO_NAME}.log"
|
||||
|
||||
# ── System install paths (set per OS) ─────────────────────────────────────────
|
||||
_system = platform.system()
|
||||
|
||||
if _system == "Darwin":
|
||||
# macOS — Homebrew convention
|
||||
APP_INSTALL_DIR = Path("/usr/local/share") / REPO_NAME
|
||||
APP_BIN_PATH = Path("/usr/local/bin") / REPO_NAME
|
||||
elif _system == "Linux":
|
||||
APP_INSTALL_DIR = Path("/usr/share") / REPO_NAME
|
||||
APP_BIN_PATH = Path("/usr/bin") / REPO_NAME
|
||||
else:
|
||||
# Fallback (Windows, FreeBSD, etc.)
|
||||
APP_INSTALL_DIR = USER_CONFIG_DIR / "app"
|
||||
APP_BIN_PATH = USER_CONFIG_DIR / "bin" / REPO_NAME
|
||||
|
||||
# ── UI theme ──────────────────────────────────────────────────────────────────
|
||||
THEME_PRIMARY = "bold magenta"
|
||||
THEME_BORDER = "bright_magenta"
|
||||
THEME_SUCCESS = "bold green"
|
||||
THEME_ERROR = "bold red"
|
||||
THEME_WARNING = "bold yellow"
|
||||
THEME_DIM = "dim white"
|
||||
THEME_ARCHIVED = "dim yellow"
|
||||
THEME_URL = "underline bright_blue"
|
||||
THEME_ACCENT = "bold cyan"
|
||||
|
||||
# ── Default config values ──────────────────────────────────────────────────────
|
||||
DEFAULT_CONFIG: dict = {
|
||||
"tools_dir": str(USER_TOOLS_DIR),
|
||||
"version": VERSION,
|
||||
"theme": "magenta",
|
||||
"show_archived": False,
|
||||
"sudo_binary": "sudo",
|
||||
"go_bin_dir": str(Path.home() / "go" / "bin"),
|
||||
"gem_bin_dir": str(Path.home() / ".gem" / "ruby"),
|
||||
}
|
||||
|
||||
# ── Privilege escalation ───────────────────────────────────────────────────────
|
||||
# Prefer doas if present (OpenBSD/some Linux setups), else sudo
|
||||
PRIV_CMD = "doas" if _shutil.which("doas") else "sudo"
|
||||
128
os_detect.py
Normal file
128
os_detect.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import platform
|
||||
import shutil
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class OSInfo:
|
||||
system: str # "linux", "macos", "windows", "unknown"
|
||||
distro_id: str = "" # "kali", "ubuntu", "arch", "fedora", etc.
|
||||
distro_like: str = "" # "debian", "rhel", etc. (from ID_LIKE)
|
||||
distro_version: str = "" # "2024.1", "22.04", etc.
|
||||
pkg_manager: str = "" # "apt-get", "pacman", "dnf", "brew", etc.
|
||||
is_root: bool = False
|
||||
home_dir: Path = field(default_factory=Path.home)
|
||||
is_wsl: bool = False # Windows Subsystem for Linux
|
||||
arch: str = "" # "x86_64", "aarch64", "arm64"
|
||||
|
||||
|
||||
def detect() -> OSInfo:
|
||||
"""
|
||||
Fully detect the current OS, distro, and available package manager.
|
||||
Never asks the user — entirely automatic.
|
||||
"""
|
||||
import os
|
||||
|
||||
system_raw = platform.system()
|
||||
system = system_raw.lower()
|
||||
if system == "darwin":
|
||||
system = "macos"
|
||||
|
||||
info = OSInfo(
|
||||
system = system,
|
||||
is_root = (os.geteuid() == 0) if hasattr(os, "geteuid") else False,
|
||||
home_dir = Path.home(),
|
||||
arch = platform.machine(),
|
||||
)
|
||||
|
||||
# ── Linux-specific ─────────────────────────────────────────────────────────
|
||||
if system == "linux":
|
||||
# Detect WSL
|
||||
try:
|
||||
info.is_wsl = "microsoft" in Path("/proc/version").read_text().lower()
|
||||
except (FileNotFoundError, PermissionError):
|
||||
pass
|
||||
|
||||
# Read /etc/os-release (standard on all modern distros)
|
||||
os_release: dict[str, str] = {}
|
||||
for path in ("/etc/os-release", "/usr/lib/os-release"):
|
||||
try:
|
||||
for line in Path(path).read_text().splitlines():
|
||||
k, _, v = line.partition("=")
|
||||
os_release[k.strip()] = v.strip().strip('"')
|
||||
break
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
info.distro_id = os_release.get("ID", "").lower()
|
||||
info.distro_like = os_release.get("ID_LIKE", "").lower()
|
||||
info.distro_version = os_release.get("VERSION_ID", "")
|
||||
|
||||
# ── Package manager detection (in priority order) ──────────────────────────
|
||||
for mgr in ("apt-get", "pacman", "dnf", "zypper", "apk", "brew", "pkg"):
|
||||
if shutil.which(mgr):
|
||||
info.pkg_manager = mgr
|
||||
break
|
||||
|
||||
return info
|
||||
|
||||
|
||||
# Module-level singleton — computed once on import
|
||||
CURRENT_OS: OSInfo = detect()
|
||||
|
||||
|
||||
# ── Per-OS package manager commands ────────────────────────────────────────────
|
||||
PACKAGE_INSTALL_CMDS: dict[str, str] = {
|
||||
"apt-get": "apt-get install -y {packages}",
|
||||
"pacman": "pacman -S --noconfirm {packages}",
|
||||
"dnf": "dnf install -y {packages}",
|
||||
"zypper": "zypper install -y {packages}",
|
||||
"apk": "apk add {packages}",
|
||||
"brew": "brew install {packages}",
|
||||
}
|
||||
|
||||
PACKAGE_UPDATE_CMDS: dict[str, str] = {
|
||||
"apt-get": "apt-get update -qq && apt-get upgrade -y",
|
||||
"pacman": "pacman -Syu --noconfirm",
|
||||
"dnf": "dnf upgrade -y",
|
||||
"zypper": "zypper update -y",
|
||||
"apk": "apk update && apk upgrade",
|
||||
"brew": "brew update && brew upgrade",
|
||||
}
|
||||
|
||||
# Core system packages needed per package manager
|
||||
REQUIRED_PACKAGES: dict[str, list[str]] = {
|
||||
"apt-get": ["git", "python3-pip", "python3-venv", "curl", "wget",
|
||||
"ruby", "ruby-dev", "golang-go", "php", "default-jre-headless"],
|
||||
"pacman": ["git", "python-pip", "curl", "wget",
|
||||
"ruby", "go", "php", "jre-openjdk-headless"],
|
||||
"dnf": ["git", "python3-pip", "curl", "wget",
|
||||
"ruby", "golang", "php", "java-17-openjdk-headless"],
|
||||
"zypper": ["git", "python3-pip", "curl", "wget", "ruby", "go", "php"],
|
||||
"brew": ["git", "python3", "curl", "wget", "ruby", "go", "php"],
|
||||
}
|
||||
|
||||
|
||||
def install_packages(packages: list[str], os_info: OSInfo | None = None) -> bool:
|
||||
"""Install system packages using the detected package manager."""
|
||||
import subprocess
|
||||
if os_info is None:
|
||||
os_info = CURRENT_OS
|
||||
|
||||
mgr = os_info.pkg_manager
|
||||
if mgr not in PACKAGE_INSTALL_CMDS:
|
||||
print(f"[warning] Unknown package manager. Install manually: {packages}")
|
||||
return False
|
||||
|
||||
cmd_template = PACKAGE_INSTALL_CMDS[mgr]
|
||||
pkg_str = " ".join(packages)
|
||||
cmd = cmd_template.format(packages=pkg_str)
|
||||
|
||||
# Prepend privilege escalation only on Linux (brew on macOS doesn't need sudo)
|
||||
if os_info.system == "linux" and not os_info.is_root:
|
||||
from constants import PRIV_CMD
|
||||
cmd = f"{PRIV_CMD} {cmd}"
|
||||
|
||||
result = subprocess.run(cmd, shell=True, check=False)
|
||||
return result.returncode == 0
|
||||
0
tools/__init__.py
Normal file
0
tools/__init__.py
Normal file
0
tools/others/__init__.py
Normal file
0
tools/others/__init__.py
Normal file
Loading…
Reference in a new issue