Skip to content

Commit 6e18a96

Browse files
author
Stew Alexander
committed
Add server/minimal mode and dry-run flag to Lazy-Linux-Tool-Installer
- Add --server flag to exclude GUI tools (perfect for server admins) - Add --dry-run/-n flag to preview installations without making changes - Mark GUI tools with requires_gui field (geany, wireshark, code, guake, tabby, xplr) - Update user consent message to show active modes - Add argparse for command-line argument parsing
1 parent 649a790 commit 6e18a96

1 file changed

Lines changed: 112 additions & 24 deletions

File tree

Lazy-Linux-Tool-Installer.py

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import sys
1111
import shutil
12+
import argparse
1213
from typing import Dict, List, Optional, Tuple
1314
from dataclasses import dataclass
1415
from enum import Enum
@@ -36,6 +37,7 @@ class Tool:
3637
requires_root: bool = True
3738
github_repo: Optional[str] = None # For eget installations
3839
classic: bool = False # For snap installations
40+
requires_gui: bool = False # Whether tool requires GUI (excluded in server mode)
3941

4042

4143
class SystemChecker:
@@ -193,21 +195,21 @@ class ToolManager:
193195
TOOLS: Dict[str, Tool] = {
194196
# Desktop GUI Apps
195197
"geany": Tool("geany", "geany", InstallMethod.APT, "geany",
196-
"GUI editor like notepad++", "Desktop GUI Apps"),
198+
"GUI editor like notepad++", "Desktop GUI Apps", requires_gui=True),
197199
"wireshark": Tool("wireshark", "wireshark", InstallMethod.APT, "wireshark",
198-
"Network packet reviewer", "Desktop GUI Apps"),
200+
"Network packet reviewer", "Desktop GUI Apps", requires_gui=True),
199201
"code": Tool("code", "code", InstallMethod.SNAP, "code",
200-
"Visual Studio Code", "Desktop GUI Apps", classic=True),
202+
"Visual Studio Code", "Desktop GUI Apps", classic=True, requires_gui=True),
201203
"guake": Tool("guake", "guake", InstallMethod.APT, "guake",
202-
"GUI terminal client", "Desktop GUI Apps"),
204+
"GUI terminal client", "Desktop GUI Apps", requires_gui=True),
203205
"tabby": Tool("tabby", "tabby", InstallMethod.EGET, "tabby",
204206
"Modern terminal emulator", "Desktop GUI Apps",
205-
github_repo="Eugeny/tabby"),
207+
github_repo="Eugeny/tabby", requires_gui=True),
206208

207209
# Terminal File Explorers
208210
"xplr": Tool("xplr", "xplr", InstallMethod.EGET, "xplr",
209211
"Very graphical file explorer", "Terminal File Explorers",
210-
github_repo="sayanarijit/xplr"),
212+
github_repo="sayanarijit/xplr", requires_gui=True),
211213
"nnn": Tool("nnn", "nnn", InstallMethod.APT, "nnn",
212214
"Efficient file explorer", "Terminal File Explorers"),
213215
"lf": Tool("lf", "lf", InstallMethod.EGET, "lf",
@@ -356,10 +358,13 @@ class ToolManager:
356358
}
357359

358360
@staticmethod
359-
def get_tools_by_category() -> Dict[str, List[Tool]]:
360-
"""Group tools by category."""
361+
def get_tools_by_category(server_mode: bool = False) -> Dict[str, List[Tool]]:
362+
"""Group tools by category, optionally filtering out GUI tools for server mode."""
361363
categories: Dict[str, List[Tool]] = {}
362364
for tool in ToolManager.TOOLS.values():
365+
# Skip GUI tools in server mode
366+
if server_mode and tool.requires_gui:
367+
continue
363368
if tool.category not in categories:
364369
categories[tool.category] = []
365370
categories[tool.category].append(tool)
@@ -371,12 +376,28 @@ def check_tool_installed(tool: Tool) -> bool:
371376
return SystemChecker.has_command(tool.command)
372377

373378
@staticmethod
374-
def install_tool(tool: Tool) -> bool:
379+
def install_tool(tool: Tool, dry_run: bool = False) -> bool:
375380
"""Install a tool using its defined method."""
376381
if tool.method == InstallMethod.BUILTIN:
377382
print(f"✓ {tool.name} is built-in (no installation needed)")
378383
return True
379384

385+
if dry_run:
386+
# In dry-run mode, just show what would be done
387+
method_str = tool.method.value
388+
if tool.method == InstallMethod.APT:
389+
print(f"[DRY RUN] Would install {tool.package} via apt")
390+
elif tool.method == InstallMethod.PIP:
391+
print(f"[DRY RUN] Would install {tool.package} via pip3")
392+
elif tool.method == InstallMethod.SNAP:
393+
classic_str = " (classic)" if tool.classic else ""
394+
print(f"[DRY RUN] Would install {tool.package} via snap{classic_str}")
395+
elif tool.method == InstallMethod.EGET:
396+
print(f"[DRY RUN] Would install {tool.command} via eget from {tool.github_repo}")
397+
elif tool.method == InstallMethod.MANUAL:
398+
print(f"[DRY RUN] Would install {tool.name} manually")
399+
return True # Pretend success in dry-run mode
400+
380401
if tool.method == InstallMethod.APT:
381402
if Installer.check_apt_available(tool.package):
382403
return Installer.install_via_apt(tool.package)
@@ -406,19 +427,35 @@ def install_tool(tool: Tool) -> bool:
406427
return False
407428

408429

409-
def get_user_consent() -> bool:
430+
def get_user_consent(server_mode: bool = False, dry_run: bool = False) -> bool:
410431
"""Get user consent once upfront - simple and clear for lazy users."""
411432
print("\n" + "="*70)
412433
print("🚀 Lazy Linux Tool Installer")
413434
print("="*70)
435+
436+
mode_info = []
437+
if server_mode:
438+
mode_info.append("🔧 SERVER MODE (CLI tools only, no GUI)")
439+
if dry_run:
440+
mode_info.append("👀 DRY RUN (preview only, no changes)")
441+
442+
if mode_info:
443+
print("\n" + " | ".join(mode_info))
444+
414445
print("\nThis script will automatically install all Linux tools from README.md")
415446
print("Perfect for lazy users - just say 'yes' and it handles everything!")
416447
print("\nWhat it does:")
417448
print(" ✓ Checks which tools you already have")
418-
print(" ✓ Installs missing tools automatically (apt, pip, eget, snap)")
449+
if dry_run:
450+
print(" 👀 Shows what would be installed (DRY RUN - no changes)")
451+
else:
452+
print(" ✓ Installs missing tools automatically (apt, pip, eget, snap)")
419453
print(" ✓ Skips tools that are already installed")
420454
print(" ✓ Organizes everything by category")
421-
print("\nYou'll be prompted for your sudo password when needed.")
455+
if server_mode:
456+
print(" 🔧 Excludes GUI tools (server-friendly)")
457+
if not dry_run:
458+
print("\nYou'll be prompted for your sudo password when needed.")
422459
print("="*70)
423460

424461
# Limit retries to prevent infinite loops on invalid input
@@ -447,8 +484,11 @@ def get_user_consent() -> bool:
447484
return False
448485

449486

450-
def update_package_lists() -> bool:
487+
def update_package_lists(dry_run: bool = False) -> bool:
451488
"""Update apt package lists."""
489+
if dry_run:
490+
print("\n[DRY RUN] Would update package lists (apt-get update)")
491+
return True
452492
print("\nUpdating package lists...")
453493
result = Installer.run_command(
454494
["sudo", "apt-get", "update"],
@@ -457,32 +497,67 @@ def update_package_lists() -> bool:
457497
return result.returncode == 0
458498

459499

500+
def parse_arguments() -> argparse.Namespace:
501+
"""Parse command-line arguments."""
502+
parser = argparse.ArgumentParser(
503+
description="Lazy Linux Tool Installer - Automatically install Linux tools from README.md",
504+
formatter_class=argparse.RawDescriptionHelpFormatter,
505+
epilog="""
506+
Examples:
507+
%(prog)s # Install all tools (default)
508+
%(prog)s --server # Install only CLI tools (no GUI)
509+
%(prog)s --dry-run # Preview what would be installed
510+
%(prog)s --server --dry-run # Preview server installation
511+
"""
512+
)
513+
parser.add_argument(
514+
"--server",
515+
action="store_true",
516+
help="Server/minimal mode: only install CLI tools (exclude GUI applications)"
517+
)
518+
parser.add_argument(
519+
"--dry-run",
520+
"-n",
521+
action="store_true",
522+
help="Dry run mode: show what would be installed without making changes"
523+
)
524+
return parser.parse_args()
525+
526+
460527
def main():
461528
"""Main execution function."""
529+
# Parse command-line arguments
530+
args = parse_arguments()
531+
server_mode = args.server
532+
dry_run = args.dry_run
533+
462534
# System check
463535
is_compatible, error_msg = SystemChecker.check_system()
464536
if not is_compatible:
465537
print(f"Error: {error_msg}", file=sys.stderr)
466538
sys.exit(1)
467539

468540
# Get user consent
469-
if not get_user_consent():
541+
if not get_user_consent(server_mode=server_mode, dry_run=dry_run):
470542
print("\nInstallation cancelled by user.")
471543
sys.exit(0)
472544

473545
# Update package lists
474-
update_package_lists()
546+
update_package_lists(dry_run=dry_run)
475547

476548
# Get tools by category for better organization
477-
tools_by_category = ToolManager.get_tools_by_category()
549+
tools_by_category = ToolManager.get_tools_by_category(server_mode=server_mode)
478550

479551
# Track installation results
480552
installed_count = 0
481553
skipped_count = 0
482554
failed_count = 0
483555

484556
print("\n" + "="*70)
485-
print("🔍 Checking and installing tools...")
557+
if dry_run:
558+
print("👀 DRY RUN: Previewing what would be installed...")
559+
else:
560+
print("🔍 Checking and installing tools...")
486561
print("="*70 + "\n")
487562

488563
# Process tools by category
@@ -496,34 +571,47 @@ def main():
496571
print(f"✓ {tool.name:30} - Already installed")
497572
skipped_count += 1
498573
else:
499-
print(f"✗ {tool.name:30} - Not installed, installing...")
500-
if ToolManager.install_tool(tool):
501-
print(f" ✓ {tool.name} installed successfully")
574+
print(f"✗ {tool.name:30} - Not installed, {'would install' if dry_run else 'installing'}...")
575+
if ToolManager.install_tool(tool, dry_run=dry_run):
576+
if dry_run:
577+
print(f" ✓ {tool.name} would be installed successfully")
578+
else:
579+
print(f" ✓ {tool.name} installed successfully")
502580
installed_count += 1
503581
else:
504582
print(f" ✗ {tool.name} installation failed")
505583
failed_count += 1
506584

507585
# Summary - clear and friendly for lazy users
508586
print("\n" + "="*70)
509-
print("✨ Installation Complete!")
587+
if dry_run:
588+
print("👀 DRY RUN Complete!")
589+
else:
590+
print("✨ Installation Complete!")
510591
print("="*70)
511592
print(f"✓ Already installed: {skipped_count}")
512-
print(f"✓ Newly installed: {installed_count}")
593+
if dry_run:
594+
print(f"👀 Would install: {installed_count}")
595+
else:
596+
print(f"✓ Newly installed: {installed_count}")
513597
if failed_count > 0:
514598
print(f"⚠ Failed: {failed_count}")
515599
else:
516600
print(f"✓ Failed: {failed_count}")
517601
print("="*70)
518602

519-
if failed_count > 0:
603+
if dry_run:
604+
print("\n👀 This was a DRY RUN - no changes were made.")
605+
print(" Run without --dry-run to actually install the tools.")
606+
elif failed_count > 0:
520607
print("\n⚠ Some tools failed to install. Check the output above for details.")
521608
print(" Some tools may require manual installation or different methods.")
522609
else:
523610
print("\n🎉 All tools installed successfully! You're all set!")
524611

525612
print("\n💡 Tip: You can run this script again anytime to check for updates.")
526-
input("\nPress Enter to exit...")
613+
if not dry_run:
614+
input("\nPress Enter to exit...")
527615
sys.exit(0)
528616

529617

0 commit comments

Comments
 (0)