-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathLazy-Linux-Tool-Installer.py
More file actions
executable file
·619 lines (547 loc) · 27.3 KB
/
Lazy-Linux-Tool-Installer.py
File metadata and controls
executable file
·619 lines (547 loc) · 27.3 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
#!/usr/bin/env python3
"""
Lazy Linux Tool Installer
Automatically installs all Linux tools from README.md - perfect for lazy users!
Just run it and it handles everything: checking, installing, and organizing tools.
"""
import subprocess
import os
import sys
import shutil
import argparse
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
class InstallMethod(Enum):
"""Installation methods for tools."""
APT = "apt"
PIP = "pip"
EGET = "eget"
SNAP = "snap"
BUILTIN = "builtin" # Already available (e.g., systemctl)
MANUAL = "manual" # Requires manual installation
@dataclass
class Tool:
"""Tool definition with installation details."""
name: str
command: str # Command to check if installed
method: InstallMethod
package: str # Package name for installation
description: str
category: str
requires_root: bool = True
github_repo: Optional[str] = None # For eget installations
classic: bool = False # For snap installations
requires_gui: bool = False # Whether tool requires GUI (excluded in server mode)
class SystemChecker:
"""Check system compatibility and requirements."""
@staticmethod
def is_debian_like() -> bool:
"""Check if system is Debian-based."""
return shutil.which("apt-get") is not None
@staticmethod
def is_root() -> bool:
"""Check if running as root."""
return os.geteuid() == 0
@staticmethod
def has_command(command: str) -> bool:
"""Check if command exists in PATH."""
return shutil.which(command) is not None
@staticmethod
def check_system() -> Tuple[bool, Optional[str]]:
"""Comprehensive system check."""
if not SystemChecker.is_debian_like():
return False, "This script requires a Debian-based system (Ubuntu, Debian, Linux Mint)"
if not SystemChecker.has_command("sudo"):
return False, "sudo is required but not found"
return True, None
class Installer:
"""Handle tool installation with different methods."""
@staticmethod
def run_command(cmd: List[str], check: bool = False, capture_output: bool = False, timeout: int = 30) -> subprocess.CompletedProcess:
"""Run command with proper error handling and timeout."""
try:
return subprocess.run(
cmd,
check=check,
capture_output=capture_output,
text=True,
timeout=timeout
)
except subprocess.TimeoutExpired:
# Return CompletedProcess with timeout exit code (124) to avoid crashes
print(f"Command timed out after {timeout}s: {' '.join(cmd)}")
return subprocess.CompletedProcess(cmd, 124)
except subprocess.CalledProcessError as e:
# Return CompletedProcess with actual error code for consistency
print(f"Error running command: {' '.join(cmd)}")
print(f"Error: {e}")
return subprocess.CompletedProcess(cmd, e.returncode)
except FileNotFoundError:
# Command missing from PATH - use standard exit code 127
print(f"Command not found: {cmd[0]}")
return subprocess.CompletedProcess(cmd, 127)
@staticmethod
def install_via_apt(package: str) -> bool:
"""Install package via apt-get."""
print(f"Installing {package} via apt...")
result = Installer.run_command(
["sudo", "apt-get", "install", "-y", package],
capture_output=True
)
return result.returncode == 0
@staticmethod
def install_via_pip(package: str) -> bool:
"""Install package via pip3."""
print(f"Installing {package} via pip3...")
result = Installer.run_command(
["pip3", "install", "--user", package],
capture_output=True
)
return result.returncode == 0
@staticmethod
def install_via_snap(package: str, classic: bool = False) -> bool:
"""Install package via snap."""
print(f"Installing {package} via snap...")
cmd = ["sudo", "snap", "install"]
if classic:
cmd.append("--classic")
cmd.append(package)
result = Installer.run_command(cmd, capture_output=True)
return result.returncode == 0
@staticmethod
def install_via_eget(repo: str, binary_name: str) -> bool:
"""Install package via eget from GitHub."""
if not SystemChecker.has_command("eget"):
print("eget not found, installing eget first...")
if not Installer.install_eget():
return False
print(f"Installing {binary_name} via eget from {repo}...")
result = Installer.run_command(
["eget", repo],
capture_output=True
)
if result.returncode == 0 and os.path.exists(binary_name):
# eget downloads to current dir; move to PATH for system-wide access
move_cmd = ["sudo", "mv", binary_name, "/usr/local/bin/"]
move_result = Installer.run_command(move_cmd)
return move_result.returncode == 0
return False
@staticmethod
def install_eget() -> bool:
"""Install eget if not present."""
print("Installing eget...")
# Timeout flags prevent hanging on slow networks; pipe directly to sh for install
result = Installer.run_command(
["sh", "-c", "curl --connect-timeout 10 --max-time 30 https://zyedidia.github.io/eget.sh | sh"],
capture_output=True,
timeout=60
)
if result.returncode == 0 and os.path.exists("eget"):
move_result = Installer.run_command(["sudo", "mv", "eget", "/usr/local/bin/"])
return move_result.returncode == 0
return False
@staticmethod
def install_croc() -> bool:
"""Install croc using official installer."""
print("Installing croc...")
result = Installer.run_command(
["sh", "-c", "curl --connect-timeout 10 --max-time 60 https://getcroc.schollz.com | bash"],
capture_output=True,
timeout=120 # Longer timeout for network operations
)
return result.returncode == 0
@staticmethod
def check_apt_available(package: str) -> bool:
"""Check if package is available in apt repositories."""
# Regex anchors (^$) ensure exact match, not substring
result = Installer.run_command(
["apt-cache", "search", "--names-only", "^" + package + "$"],
capture_output=True
)
return result.returncode == 0 and package in result.stdout
class ToolManager:
"""Manage tool definitions and installation."""
# Define all tools from README.md
TOOLS: Dict[str, Tool] = {
# Desktop GUI Apps
"geany": Tool("geany", "geany", InstallMethod.APT, "geany",
"GUI editor like notepad++", "Desktop GUI Apps", requires_gui=True),
"wireshark": Tool("wireshark", "wireshark", InstallMethod.APT, "wireshark",
"Network packet reviewer", "Desktop GUI Apps", requires_gui=True),
"code": Tool("code", "code", InstallMethod.SNAP, "code",
"Visual Studio Code", "Desktop GUI Apps", classic=True, requires_gui=True),
"guake": Tool("guake", "guake", InstallMethod.APT, "guake",
"GUI terminal client", "Desktop GUI Apps", requires_gui=True),
"tabby": Tool("tabby", "tabby", InstallMethod.EGET, "tabby",
"Modern terminal emulator", "Desktop GUI Apps",
github_repo="Eugeny/tabby", requires_gui=True),
# Terminal File Explorers
"xplr": Tool("xplr", "xplr", InstallMethod.EGET, "xplr",
"Very graphical file explorer", "Terminal File Explorers",
github_repo="sayanarijit/xplr", requires_gui=True),
"nnn": Tool("nnn", "nnn", InstallMethod.APT, "nnn",
"Efficient file explorer", "Terminal File Explorers"),
"lf": Tool("lf", "lf", InstallMethod.EGET, "lf",
"Cross-platform file explorer", "Terminal File Explorers",
github_repo="gokcehan/lf"),
# LS-like Directory Viewers
"eza": Tool("eza", "eza", InstallMethod.EGET, "eza",
"Modern ls replacement (exa successor)", "LS-like Directory Viewers",
github_repo="eza-community/eza"),
"lsd": Tool("lsd", "lsd", InstallMethod.EGET, "lsd",
"ls clone with icons", "LS-like Directory Viewers",
github_repo="Peltoche/lsd"),
# Text Editors and Viewers
"micro": Tool("micro", "micro", InstallMethod.EGET, "micro",
"User-friendly terminal editor", "Text Editors and Viewers",
github_repo="zyedidia/micro"),
"ne": Tool("ne", "ne", InstallMethod.APT, "ne",
"Terminal editor like nano", "Text Editors and Viewers"),
"vim": Tool("vim", "vim", InstallMethod.APT, "vim",
"VI editor with extras", "Text Editors and Viewers"),
"nvim": Tool("nvim", "nvim", InstallMethod.APT, "neovim",
"Modern vim alternative", "Text Editors and Viewers"),
"bat": Tool("bat", "batcat", InstallMethod.APT, "bat",
"cat clone with syntax highlighting", "Text Editors and Viewers"),
# Process Explorers
"glances": Tool("glances", "glances", InstallMethod.PIP, "glances",
"System info in one glance", "Process Explorers"),
"htop": Tool("htop", "htop", InstallMethod.APT, "htop",
"Supercharged top clone", "Process Explorers"),
"btop": Tool("btop", "btop", InstallMethod.EGET, "btop",
"TUI CLI graphics process monitor", "Process Explorers",
github_repo="aristocratos/btop"),
"bottom": Tool("bottom", "btm", InstallMethod.EGET, "btm",
"btop-inspired process monitor", "Process Explorers",
github_repo="ClementTsang/bottom"),
# Network-Related Apps
"croc": Tool("croc", "croc", InstallMethod.MANUAL, "croc",
"Secure file transfer", "Network-Related Apps"),
"network-manager": Tool("nmtui", "nmtui", InstallMethod.APT, "network-manager",
"Terminal Network Manager", "Network-Related Apps"),
"hping3": Tool("hping3", "hping3", InstallMethod.APT, "hping3",
"Advanced ping tool", "Network-Related Apps"),
"nmap": Tool("nmap", "nmap", InstallMethod.APT, "nmap",
"Network scanner", "Network-Related Apps"),
"bmon": Tool("bmon", "bmon", InstallMethod.APT, "bmon",
"TUI network bandwidth monitor", "Network-Related Apps"),
"mtr": Tool("mtr", "mtr", InstallMethod.APT, "mtr-tiny",
"Traceroute and ping combined", "Network-Related Apps"),
"gping": Tool("gping", "gping", InstallMethod.EGET, "gping",
"Ping with graph", "Network-Related Apps",
github_repo="orf/gping"),
"dog": Tool("dog", "dog", InstallMethod.EGET, "dog",
"Modern dig alternative", "Network-Related Apps",
github_repo="ogham/dog"),
"neoss": Tool("neoss", "neoss", InstallMethod.EGET, "neoss",
"Modern ss alternative", "Network-Related Apps",
github_repo="PabloLec/neoss"),
# Misc CLI Terminal Apps
"systemctl": Tool("systemctl", "systemctl", InstallMethod.BUILTIN, "systemd",
"Built-in systemd service manager", "Misc CLI Terminal Apps",
requires_root=False),
"ncdu": Tool("ncdu", "ncdu", InstallMethod.APT, "ncdu",
"Terminal disk space viewer", "Misc CLI Terminal Apps"),
"dust": Tool("dust", "dust", InstallMethod.EGET, "dust",
"Intuitive du with bar charts", "Misc CLI Terminal Apps",
github_repo="bootandy/dust"),
"duf": Tool("duf", "duf", InstallMethod.EGET, "duf",
"Disk utility with graphs", "Misc CLI Terminal Apps",
github_repo="muesli/duf"),
"lynis": Tool("lynis", "lynis", InstallMethod.APT, "lynis",
"Linux security auditing", "Misc CLI Terminal Apps"),
"apt-show-versions": Tool("apt-show-versions", "apt-show-versions",
InstallMethod.APT, "apt-show-versions",
"Show package versions", "Misc CLI Terminal Apps"),
"nala": Tool("nala", "nala", InstallMethod.APT, "nala",
"User-friendly apt frontend", "Misc CLI Terminal Apps"),
"fd": Tool("fd", "fdfind", InstallMethod.APT, "fd-find",
"Fast find alternative", "Misc CLI Terminal Apps"),
"fish": Tool("fish", "fish", InstallMethod.APT, "fish",
"Friendly interactive shell", "Misc CLI Terminal Apps"),
"starship": Tool("starship", "starship", InstallMethod.EGET, "starship",
"Customizable shell prompt", "Misc CLI Terminal Apps",
github_repo="starship/starship"),
"zoxide": Tool("zoxide", "zoxide", InstallMethod.EGET, "zoxide",
"Smarter cd command", "Misc CLI Terminal Apps",
github_repo="ajeetdsouza/zoxide"),
"atuin": Tool("atuin", "atuin", InstallMethod.EGET, "atuin",
"Magical shell history", "Misc CLI Terminal Apps",
github_repo="atuinsh/atuin"),
"tig": Tool("tig", "tig", InstallMethod.APT, "tig",
"TUI git client", "Misc CLI Terminal Apps"),
"lazygit": Tool("lazygit", "lazygit", InstallMethod.EGET, "lazygit",
"Simple terminal UI for git", "Misc CLI Terminal Apps",
github_repo="jesseduffield/lazygit"),
"delta": Tool("delta", "delta", InstallMethod.EGET, "delta",
"Syntax-highlighting git pager", "Misc CLI Terminal Apps",
github_repo="dandavison/delta"),
"miller": Tool("miller", "mlr", InstallMethod.APT, "miller",
"JSON/CSV processor", "Misc CLI Terminal Apps"),
"most": Tool("most", "most", InstallMethod.APT, "most",
"Better pager than less/more", "Misc CLI Terminal Apps"),
"tldr": Tool("tldr", "tldr", InstallMethod.PIP, "tldr",
"Simplified man pages", "Misc CLI Terminal Apps"),
"lazydocker": Tool("lazydocker", "lazydocker", InstallMethod.EGET, "lazydocker",
"TUI Docker manager", "Misc CLI Terminal Apps",
github_repo="jesseduffield/lazydocker"),
"json-tui": Tool("json-tui", "json-tui", InstallMethod.EGET, "json-tui",
"JSON file viewer", "Misc CLI Terminal Apps",
github_repo="ArthurSonzogni/json-tui"),
"jc": Tool("jc", "jc", InstallMethod.APT, "jc",
"Parse command output to JSON", "Misc CLI Terminal Apps"),
"visidata": Tool("visidata", "visidata", InstallMethod.PIP, "visidata",
"CSV/data viewer", "Misc CLI Terminal Apps"),
"eg": Tool("eg", "eg", InstallMethod.EGET, "eg",
"TLDR-like command helper", "Misc CLI Terminal Apps",
github_repo="srsudar/eg"),
"procs": Tool("procs", "procs", InstallMethod.EGET, "procs",
"Modern ps replacement", "Misc CLI Terminal Apps",
github_repo="dalance/procs"),
"sd": Tool("sd", "sd", InstallMethod.EGET, "sd",
"Modern sed replacement", "Misc CLI Terminal Apps",
github_repo="chmln/sd"),
"ripgrep": Tool("ripgrep", "rg", InstallMethod.APT, "ripgrep",
"Fast text search tool", "Misc CLI Terminal Apps"),
"ripgrep-all": Tool("ripgrep-all", "rga", InstallMethod.EGET, "rga",
"ripgrep for all file types", "Misc CLI Terminal Apps",
github_repo="phiresky/ripgrep-all"),
"fzf": Tool("fzf", "fzf", InstallMethod.APT, "fzf",
"Command-line fuzzy finder", "Misc CLI Terminal Apps"),
"fastfetch": Tool("fastfetch", "fastfetch", InstallMethod.EGET, "fastfetch",
"System info display", "Misc CLI Terminal Apps",
github_repo="fastfetch-cli/fastfetch"),
"pandoc": Tool("pandoc", "pandoc", InstallMethod.APT, "pandoc",
"Document converter", "Misc CLI Terminal Apps"),
"hyperfine": Tool("hyperfine", "hyperfine", InstallMethod.EGET, "hyperfine",
"Command benchmarking tool", "Misc CLI Terminal Apps",
github_repo="sharkdp/hyperfine"),
"just": Tool("just", "just", InstallMethod.EGET, "just",
"Command runner (make alternative)", "Misc CLI Terminal Apps",
github_repo="casey/just"),
}
@staticmethod
def get_tools_by_category(server_mode: bool = False) -> Dict[str, List[Tool]]:
"""Group tools by category, optionally filtering out GUI tools for server mode."""
categories: Dict[str, List[Tool]] = {}
for tool in ToolManager.TOOLS.values():
# Skip GUI tools in server mode
if server_mode and tool.requires_gui:
continue
if tool.category not in categories:
categories[tool.category] = []
categories[tool.category].append(tool)
return categories
@staticmethod
def check_tool_installed(tool: Tool) -> bool:
"""Check if tool is installed."""
return SystemChecker.has_command(tool.command)
@staticmethod
def install_tool(tool: Tool, dry_run: bool = False) -> bool:
"""Install a tool using its defined method."""
if tool.method == InstallMethod.BUILTIN:
print(f"✓ {tool.name} is built-in (no installation needed)")
return True
if dry_run:
# In dry-run mode, just show what would be done
method_str = tool.method.value
if tool.method == InstallMethod.APT:
print(f"[DRY RUN] Would install {tool.package} via apt")
elif tool.method == InstallMethod.PIP:
print(f"[DRY RUN] Would install {tool.package} via pip3")
elif tool.method == InstallMethod.SNAP:
classic_str = " (classic)" if tool.classic else ""
print(f"[DRY RUN] Would install {tool.package} via snap{classic_str}")
elif tool.method == InstallMethod.EGET:
print(f"[DRY RUN] Would install {tool.command} via eget from {tool.github_repo}")
elif tool.method == InstallMethod.MANUAL:
print(f"[DRY RUN] Would install {tool.name} manually")
return True # Pretend success in dry-run mode
if tool.method == InstallMethod.APT:
if Installer.check_apt_available(tool.package):
return Installer.install_via_apt(tool.package)
else:
print(f"⚠ {tool.name} not available in apt repositories")
return False
elif tool.method == InstallMethod.PIP:
return Installer.install_via_pip(tool.package)
elif tool.method == InstallMethod.SNAP:
return Installer.install_via_snap(tool.package, classic=tool.classic)
elif tool.method == InstallMethod.EGET:
if tool.github_repo:
return Installer.install_via_eget(tool.github_repo, tool.command)
else:
print(f"⚠ {tool.name} missing GitHub repository information")
return False
elif tool.method == InstallMethod.MANUAL:
if tool.name == "croc":
return Installer.install_croc()
print(f"⚠ {tool.name} requires manual installation")
return False
return False
def get_user_consent(server_mode: bool = False, dry_run: bool = False) -> bool:
"""Get user consent once upfront - simple and clear for lazy users."""
print("\n" + "="*70)
print("🚀 Lazy Linux Tool Installer")
print("="*70)
mode_info = []
if server_mode:
mode_info.append("🔧 SERVER MODE (CLI tools only, no GUI)")
if dry_run:
mode_info.append("👀 DRY RUN (preview only, no changes)")
if mode_info:
print("\n" + " | ".join(mode_info))
print("\nThis script will automatically install all Linux tools from README.md")
print("Perfect for lazy users - just say 'yes' and it handles everything!")
print("\nWhat it does:")
print(" ✓ Checks which tools you already have")
if dry_run:
print(" 👀 Shows what would be installed (DRY RUN - no changes)")
else:
print(" ✓ Installs missing tools automatically (apt, pip, eget, snap)")
print(" ✓ Skips tools that are already installed")
print(" ✓ Organizes everything by category")
if server_mode:
print(" 🔧 Excludes GUI tools (server-friendly)")
if not dry_run:
print("\nYou'll be prompted for your sudo password when needed.")
print("="*70)
# Limit retries to prevent infinite loops on invalid input
max_attempts = 5
attempts = 0
while attempts < max_attempts:
try:
response = input("\nDo you want to proceed? [y/N]: ").strip().lower()
if response in ('y', 'yes'):
return True
elif response in ('n', 'no', ''):
return False
else:
attempts += 1
if attempts < max_attempts:
print("Please enter 'y' for yes or 'n' for no")
else:
print("Maximum attempts reached. Defaulting to 'no'.")
return False
except (EOFError, KeyboardInterrupt):
# Handle Ctrl+C and EOF gracefully without crashing
print("\n\nInterrupted by user. Exiting.")
return False
return False
def update_package_lists(dry_run: bool = False) -> bool:
"""Update apt package lists."""
if dry_run:
print("\n[DRY RUN] Would update package lists (apt-get update)")
return True
print("\nUpdating package lists...")
result = Installer.run_command(
["sudo", "apt-get", "update"],
capture_output=True
)
return result.returncode == 0
def parse_arguments() -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(
description="Lazy Linux Tool Installer - Automatically install Linux tools from README.md",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s # Install all tools (default)
%(prog)s --server # Install only CLI tools (no GUI)
%(prog)s --dry-run # Preview what would be installed
%(prog)s --server --dry-run # Preview server installation
"""
)
parser.add_argument(
"--server",
action="store_true",
help="Server/minimal mode: only install CLI tools (exclude GUI applications)"
)
parser.add_argument(
"--dry-run",
"-n",
action="store_true",
help="Dry run mode: show what would be installed without making changes"
)
return parser.parse_args()
def main():
"""Main execution function."""
# Parse command-line arguments
args = parse_arguments()
server_mode = args.server
dry_run = args.dry_run
# System check
is_compatible, error_msg = SystemChecker.check_system()
if not is_compatible:
print(f"Error: {error_msg}", file=sys.stderr)
sys.exit(1)
# Get user consent
if not get_user_consent(server_mode=server_mode, dry_run=dry_run):
print("\nInstallation cancelled by user.")
sys.exit(0)
# Update package lists
update_package_lists(dry_run=dry_run)
# Get tools by category for better organization
tools_by_category = ToolManager.get_tools_by_category(server_mode=server_mode)
# Track installation results
installed_count = 0
skipped_count = 0
failed_count = 0
print("\n" + "="*70)
if dry_run:
print("👀 DRY RUN: Previewing what would be installed...")
else:
print("🔍 Checking and installing tools...")
print("="*70 + "\n")
# Process tools by category
for category, tools in tools_by_category.items():
print(f"\n📦 [{category}]")
print("-" * 70)
# Sort alphabetically for consistent output
for tool in sorted(tools, key=lambda t: t.name):
if ToolManager.check_tool_installed(tool):
print(f"✓ {tool.name:30} - Already installed")
skipped_count += 1
else:
print(f"✗ {tool.name:30} - Not installed, {'would install' if dry_run else 'installing'}...")
if ToolManager.install_tool(tool, dry_run=dry_run):
if dry_run:
print(f" ✓ {tool.name} would be installed successfully")
else:
print(f" ✓ {tool.name} installed successfully")
installed_count += 1
else:
print(f" ✗ {tool.name} installation failed")
failed_count += 1
# Summary - clear and friendly for lazy users
print("\n" + "="*70)
if dry_run:
print("👀 DRY RUN Complete!")
else:
print("✨ Installation Complete!")
print("="*70)
print(f"✓ Already installed: {skipped_count}")
if dry_run:
print(f"👀 Would install: {installed_count}")
else:
print(f"✓ Newly installed: {installed_count}")
if failed_count > 0:
print(f"⚠ Failed: {failed_count}")
else:
print(f"✓ Failed: {failed_count}")
print("="*70)
if dry_run:
print("\n👀 This was a DRY RUN - no changes were made.")
print(" Run without --dry-run to actually install the tools.")
elif failed_count > 0:
print("\n⚠ Some tools failed to install. Check the output above for details.")
print(" Some tools may require manual installation or different methods.")
else:
print("\n🎉 All tools installed successfully! You're all set!")
print("\n💡 Tip: You can run this script again anytime to check for updates.")
if not dry_run:
input("\nPress Enter to exit...")
sys.exit(0)
if __name__ == "__main__":
main()