Optimized cli script

This commit is contained in:
Kevin Veen-Birkenbach 2025-07-10 21:40:57 +02:00
parent c160c58a5c
commit 4de60d4162
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
3 changed files with 58 additions and 29 deletions

69
main.py
View File

@ -49,7 +49,6 @@ def list_cli_commands(cli_dir):
def extract_description_via_help(cli_script_path): def extract_description_via_help(cli_script_path):
try: try:
# derive module name from script path
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
cli_dir = os.path.join(script_dir, "cli") cli_dir = os.path.join(script_dir, "cli")
rel = os.path.relpath(cli_script_path, cli_dir) rel = os.path.relpath(cli_script_path, cli_dir)
@ -84,26 +83,29 @@ def play_start_intro():
Sound.play_cymais_intro_sound() Sound.play_cymais_intro_sound()
def failure_with_warning_loop(): def failure_with_warning_loop(no_signal, sound_enabled):
Sound.play_finished_failed_sound() if sound_enabled and not no_signal:
print("Warning: command failed. Press Ctrl+C to stop sound warnings.") Sound.play_finished_failed_sound()
print("Warning: command failed. Press Ctrl+C to stop warnings.")
try: try:
while True: while True:
Sound.play_warning_sound() if sound_enabled:
Sound.play_warning_sound()
except KeyboardInterrupt: except KeyboardInterrupt:
print("Warnings stopped by user.") print("Warnings stopped by user.")
if __name__ == "__main__": if __name__ == "__main__":
# Early parse special flags # Parse flags
no_sound = '--no-sound' in sys.argv and (sys.argv.remove('--no-sound') or True) sound_enabled = '--sound' in sys.argv and (sys.argv.remove('--sound') or True)
no_signal = '--no-signal' in sys.argv and (sys.argv.remove('--no-signal') or True)
log_enabled = '--log' in sys.argv and (sys.argv.remove('--log') or True) log_enabled = '--log' in sys.argv and (sys.argv.remove('--log') or True)
git_clean = '--git-clean' in sys.argv and (sys.argv.remove('--git-clean') or True) git_clean = '--git-clean' in sys.argv and (sys.argv.remove('--git-clean') or True)
infinite = '--infinite' in sys.argv and (sys.argv.remove('--infinite') or True) infinite = '--infinite' in sys.argv and (sys.argv.remove('--infinite') or True)
# Segfault handler # Segfault handler
def segv_handler(signum, frame): def segv_handler(signum, frame):
if not no_sound: if sound_enabled and not no_signal:
Sound.play_finished_failed_sound() Sound.play_finished_failed_sound()
try: try:
while True: while True:
@ -114,8 +116,8 @@ if __name__ == "__main__":
sys.exit(1) sys.exit(1)
signal.signal(signal.SIGSEGV, segv_handler) signal.signal(signal.SIGSEGV, segv_handler)
# Play intro sounds asynchronously # Play intro melody if requested
if not no_sound: if sound_enabled:
threading.Thread(target=play_start_intro, daemon=True).start() threading.Thread(target=play_start_intro, daemon=True).start()
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
@ -132,33 +134,53 @@ if __name__ == "__main__":
# Global help # Global help
if not args or args[0] in ('-h', '--help'): if not args or args[0] in ('-h', '--help'):
print("CyMaIS CLI proxy to tools in ./cli/") print("CyMaIS CLI proxy to tools in ./cli/")
print("Usage: cymais [--no-sound] [--log] [--git-clean] [--infinite] <command> [options]") print("\nUsage: cymais [--sound] [--no-signal] [--log] [--git-clean] [--infinite] <command> [options]")
print("Options:") print("\nOptions:")
print(" --no-sound Suppress all sounds during execution") print(" --sound Play startup melody and warning sounds")
print(" --no-signal Suppress success/failure signals")
print(" --log Log all proxied command output to logfile.log") print(" --log Log all proxied command output to logfile.log")
print(" --git-clean Remove all Git-ignored files before running") print(" --git-clean Remove all Git-ignored files before running")
print(" --infinite Run the proxied command in an infinite loop") print(" --infinite Run the proxied command in an infinite loop")
print(" -h, --help Show this help message and exit") print(" -h, --help Show this help message and exit")
print()
print("Available commands:") print("Available commands:")
print()
current_folder = None current_folder = None
for folder, cmd in available: for folder, cmd in available:
if folder != current_folder: if folder != current_folder:
if folder: if folder:
print(f"{folder}/") print(f"{folder}/\n")
current_folder = folder current_folder = folder
desc = extract_description_via_help( desc = extract_description_via_help(
os.path.join(cli_dir, *(folder.split('/') if folder else []), f"{cmd}.py") os.path.join(cli_dir, *(folder.split('/') if folder else []), f"{cmd}.py")
) )
# Slight indent for subcommands print(format_command_help(cmd, desc, indent=2),"\n")
print(format_command_help(cmd, desc, indent=2))
print()
print("🔗 You can chain subcommands by specifying nested directories,")
print(" e.g. `cymais generate defaults applications` →")
print(" corresponds to `cli/generate/defaults/applications.py`.")
sys.exit(0) sys.exit(0)
# Directory-specific help: `cymais foo --help` shows commands in cli/foo/
if len(args) > 1 and args[-1] in ('-h', '--help'):
dir_parts = args[:-1]
candidate_dir = os.path.join(cli_dir, *dir_parts)
if os.path.isdir(candidate_dir):
print(f"Overview of commands in: {'/'.join(dir_parts)}\n")
for folder, cmd in available:
if folder == "/".join(dir_parts):
desc = extract_description_via_help(
os.path.join(candidate_dir, f"{cmd}.py")
)
print(format_command_help(cmd, desc, indent=2))
sys.exit(0)
# Per-command help # Per-command help
for n in range(len(args), 0, -1): for n in range(len(args), 0, -1):
candidate = os.path.join(cli_dir, *args[:n]) + ".py" candidate = os.path.join(cli_dir, *args[:n]) + ".py"
if os.path.isfile(candidate) and len(args) > n and args[n] in ('-h', '--help'): if os.path.isfile(candidate) and len(args) > n and args[n] in ('-h', '--help'):
# derive module
rel = os.path.relpath(candidate, cli_dir) rel = os.path.relpath(candidate, cli_dir)
module = "cli." + rel[:-3].replace(os.sep, ".") module = "cli." + rel[:-3].replace(os.sep, ".")
subprocess.run([sys.executable, "-m", module, args[n]]) subprocess.run([sys.executable, "-m", module, args[n]])
@ -188,7 +210,7 @@ if __name__ == "__main__":
timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') timestamp = datetime.now().strftime('%Y%m%dT%H%M%S')
log_file_path = os.path.join(log_dir, f'{timestamp}.log') log_file_path = os.path.join(log_dir, f'{timestamp}.log')
log_file = open(log_file_path, 'a', encoding='utf-8') log_file = open(log_file_path, 'a', encoding='utf-8')
print(f"📖 Tip: Log file created at {log_file_path}") print(f"Tip: Log file created at {log_file_path}")
full_cmd = [sys.executable, "-m", module] + cli_args full_cmd = [sys.executable, "-m", module] + cli_args
@ -226,23 +248,22 @@ if __name__ == "__main__":
log_file.close() log_file.close()
if rc != 0: if rc != 0:
print(f"Command '{os.path.basename(script_path)}' failed with exit code {rc}.") failure_with_warning_loop(no_signal, sound_enabled)
failure_with_warning_loop()
sys.exit(rc) sys.exit(rc)
else: else:
if not no_sound: if sound_enabled and not no_signal:
Sound.play_finished_successfully_sound() Sound.play_finished_successfully_sound()
return True return True
except Exception as e: except Exception as e:
print(f"Exception running command: {e}") print(f"Exception running command: {e}")
failure_with_warning_loop() failure_with_warning_loop(no_signal, sound_enabled)
sys.exit(1) sys.exit(1)
if infinite: if infinite:
print("♾️ Starting infinite execution mode...") print("Starting infinite execution mode...")
count = 1 count = 1
while True: while True:
print(f"🔄 Execution #{count}") print(f"Run #{count}")
run_once() run_once()
count += 1 count += 1
else: else:

View File

@ -101,6 +101,19 @@
name: update name: update
when: mode_update | bool when: mode_update | bool
- name: "Integrate Docker Role includes"
include_tasks: "./tasks/groups/{{ item }}-roles.yml"
loop:
- core
- drv
- gen
- net
- alert
- mon
- maint
loop_control:
label: "{{ item }}-roles.yml"
- name: setup standard wireguard - name: setup standard wireguard
when: ('wireguard_server' | application_allowed(group_names, allowed_applications)) when: ('wireguard_server' | application_allowed(group_names, allowed_applications))
include_role: include_role:

View File

@ -18,8 +18,3 @@
- web - web
loop_control: loop_control:
label: "{{ item }}-roles.yml" label: "{{ item }}-roles.yml"
- name: "setup corporate identity"
include_role:
name: util-srv-corporate-identity
when: ('corporate_identity' | application_allowed(group_names, allowed_applications))