99import os
1010import sys
1111import shutil
12+ import argparse
1213from typing import Dict , List , Optional , Tuple
1314from dataclasses import dataclass
1415from 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
4143class 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 ("\n This 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 ("\n What 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 ("\n You'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 ("\n You'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 ("\n Updating 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+
460527def 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 ("\n Installation 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 ("\n Press Enter to exit..." )
613+ if not dry_run :
614+ input ("\n Press Enter to exit..." )
527615 sys .exit (0 )
528616
529617
0 commit comments