From 9131ace1ac59f0ee8674ecc095afaae5385fd7b9 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Wed, 4 Feb 2026 14:29:04 -0800 Subject: [PATCH 01/13] bug fix --- gecko | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gecko b/gecko index a83be4b..2f77eaa 100755 --- a/gecko +++ b/gecko @@ -59,7 +59,7 @@ def run_triage(): group.add_argument( "-u", "-user", - action="store_true", + type=str, help="Input username to better track issues" ) From effe47a3cf11b322b0907f7055b8d194c68b422e Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Wed, 4 Feb 2026 14:56:04 -0800 Subject: [PATCH 02/13] bug fixes and args for user added --- gecko | 39 +++++++++++++++++++---------------- triage_package/triage_tool.py | 9 ++++---- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/gecko b/gecko index 2f77eaa..94797e8 100755 --- a/gecko +++ b/gecko @@ -42,23 +42,23 @@ def run_triage(): ) # Mutually exclusive: either initialize or provide a message - group = parser.add_mutually_exclusive_group(required=True) + #group = parser.add_mutually_exclusive_group(required=True) - group.add_argument( + parser.add_argument( "-init","--initialize", action="store_true", help="Initialize the application" ) - group.add_argument( + parser.add_argument( "-m", "--message", type=str, help="Run the triage workflow with a message describing the issue. " "Example: --message 'GPU overheating issue observed today'" ) - group.add_argument( - "-u", "-user", + parser.add_argument( + "-u", "--user", type=str, help="Input username to better track issues" ) @@ -66,24 +66,27 @@ def run_triage(): args = parser.parse_args() if args.initialize: - gecko = Triagetools(config=CONFIG_PATH, init=True) - print('\nInitialization complete!') - print('You can now run the GUI or use:\n gecko -m "your issue message" \n') + if not args.message and not args.user: + gecko = Triagetools(config=CONFIG_PATH, init=True) + print('\nInitialization complete!') + print('You can now run the GUI or use:\n gecko -m "your issue message" \n') elif args.message and args.user: - user_message = f"USER: {args.user}\nMESSAGE: {args.message}" - gecko = Triagetools(config=CONFIG_PATH, message=user_message) - print(f"Running triage workflow for user: {args.user}\nwith the message: {args.message}\n") - gecko.gather_system_info() - gecko.gather_logs() - gecko.comb_logs() - gecko.take_screenshots() - gecko.grab_science_image() - gecko.compress_report() + if not args.initialize: + user_message = f"USER: {args.user}\nMESSAGE: {args.message}" + gecko = Triagetools(config=CONFIG_PATH, message=user_message) + print(f"Running triage workflow for user: {args.user}\n" + "with the message: {args.message}\n") + gecko.gather_system_info() + gecko.gather_logs() + gecko.comb_logs() + gecko.take_screenshots() + gecko.grab_science_image() + gecko.compress_report() #if gecko.email_alerts: # gecko.send_report() - print("\nTriage workflow complete!") + print("\nTriage workflow complete!") else: print("\n\nYou need to include a user and a message in your report.") diff --git a/triage_package/triage_tool.py b/triage_package/triage_tool.py index 1dfa2e7..01957fe 100755 --- a/triage_package/triage_tool.py +++ b/triage_package/triage_tool.py @@ -23,15 +23,14 @@ import shutil import configparser import smtplib -from pathlib import Path from email.message import EmailMessage import re from datetime import datetime, timezone, timedelta import glob -import threading -import socket +import threading #pylint: disable=W0611 +import socket #pylint: disable=W0611 import psutil -from vncdotool import api +from vncdotool import api #pylint: disable=W0611 class Triagetools(object): """Triage tool for bug catching and error reporting""" @@ -200,7 +199,7 @@ def take_screenshots(self): try: out = self.capture_session(s) print(f"Captured Session: {s} -> {out}") - except Exception as e: + except Exception as e: #pylint: disable=W0718 print(f"Failed to capture -> {s}: {e}") def capture_session(self, session): From 5540762917933c06d1c84cdc9ca6e8397768c8e0 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Thu, 5 Feb 2026 10:54:25 -0800 Subject: [PATCH 03/13] simple email implementation --- triage_package/triage.ini | 28 ++++++++++++------------ triage_package/triage_tool.py | 40 ++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/triage_package/triage.ini b/triage_package/triage.ini index 2a2b249..a321c66 100644 --- a/triage_package/triage.ini +++ b/triage_package/triage.ini @@ -30,16 +30,16 @@ help_text = time_pattern: ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?) email_alerts: false recipient_email: eng@observatory.edu - sender_email: hello@gmail.com - sender_password: password +# sender_email: hello@gmail.com +# sender_password: password report_path = time_pattern = ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?) email_alerts = recipient_email = -sender_email = -sender_password = +#sender_email = +#sender_password = [Machine] help_text = @@ -52,15 +52,15 @@ help_text = cpu_threshold = memory_threshold = -[VNC] -help_text = - Please provide VNC details: - (EXAMPLES) - host: host.provider.com/host.iden.edu - password: Password1234 - vnc_sessions: 1,2,3,4,5,12 +#[VNC] +#help_text = +# Please provide VNC details: +# (EXAMPLES) +# host: host.provider.com/host.iden.edu +# password: Password1234 +# vnc_sessions: 1,2,3,4,5,12 -host = -password = -vnc_sessions = +#host = +#password = +#vnc_sessions = diff --git a/triage_package/triage_tool.py b/triage_package/triage_tool.py index 01957fe..ca75967 100755 --- a/triage_package/triage_tool.py +++ b/triage_package/triage_tool.py @@ -22,7 +22,7 @@ import tarfile import shutil import configparser -import smtplib +#import smtplib from email.message import EmailMessage import re from datetime import datetime, timezone, timedelta @@ -363,19 +363,39 @@ def send_report(self): # Attach PNG images recursively from the reports_path image_files = glob.glob(os.path.join(self.reports_path, '**', '*.png'), recursive=True) - for file in image_files: - with open(file, 'rb') as fp: - img_data = fp.read() - filename = os.path.basename(file) - msg.add_attachment(img_data, maintype='image', subtype='png', filename=filename) + #for file in image_files: + # with open(file, 'rb') as fp: + # img_data = fp.read() + # filename = os.path.basename(file) + # msg.add_attachment(img_data, maintype='image', subtype='png', filename=filename) ## Send email using local SMTP server #with smtplib.SMTP('localhost') as sender: # sender.send_message(msg) # Connect to Gmail SMTP server - with smtplib.SMTP_SSL('smtp.outlook.com', 465) as smtp: - smtp.login(self.sender_email, self.sender_password) # use an App Password - smtp.send_message(msg) + #with smtplib.SMTP_SSL('smtp.outlook.com', 465) as smtp: + # smtp.login(self.sender_email, self.sender_password) # use an App Password + # smtp.send_message(msg) + #print(f"Report sent to {self.target_email}") + + for file in image_files: + with open(file, "rb") as fp: + msg.add_attachment( + fp.read(), + maintype="image", + subtype="png", + filename=os.path.basename(file) + ) + + # Send using local sendmail instead of SMTP + try: + subprocess.run( + ["/usr/sbin/sendmail", "-t", "-oi"], + input=msg.as_bytes(), + check=True + ) + print(f"Report sent to {self.target_email}") - print(f"Report sent to {self.target_email}") + except Exception as e: #pylint: disable=W0718 + print("Failed to send report:", e) From af2e1fce4d74963edea8c5f622927829e731bd9d Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Thu, 5 Feb 2026 13:03:02 -0800 Subject: [PATCH 04/13] comment out until we implement smtp --- triage_package/triage_tool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/triage_package/triage_tool.py b/triage_package/triage_tool.py index ca75967..585a2c0 100755 --- a/triage_package/triage_tool.py +++ b/triage_package/triage_tool.py @@ -107,8 +107,8 @@ def load_config(self, config): self.email_alerts = self.config.getboolean("Report", "email_alerts") if self.email_alerts: self.target_email = self.config["Report"]["recipient_email"] - self.sender_email = self.config["Report"]["sender_email"] - self.sender_password = self.config["Report"]["sender_password"] + #self.sender_email = self.config["Report"]["sender_email"] + #self.sender_password = self.config["Report"]["sender_password"] self.r_path = self.config["Report"]["report_path"] self.log_dir = self.config["Logs"]["logs_dir"] self.science_dir = self.config["Logs"]["science_dir"] From 25738a51ce3bcdffdc41607ccee02347865d4151 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 13:17:36 -0800 Subject: [PATCH 05/13] improved tar.gz and email implementation --- triage_package/triage_tool.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/triage_package/triage_tool.py b/triage_package/triage_tool.py index 585a2c0..c27a4da 100755 --- a/triage_package/triage_tool.py +++ b/triage_package/triage_tool.py @@ -22,7 +22,8 @@ import tarfile import shutil import configparser -#import smtplib +import smtplib +from pathlib import Path from email.message import EmailMessage import re from datetime import datetime, timezone, timedelta @@ -44,6 +45,7 @@ def __init__(self, config: str, message:str = "", init = False): self.cutoff = datetime.now().replace(tzinfo=None) - timedelta(hours=24) self.message = message self.time_pattern = r"^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)" + self.tar_filename = "" # Create a ConfigParser object self.config = configparser.ConfigParser() @@ -293,8 +295,24 @@ def comb_logs(self): def compress_report(self): '''Compresses report file into a tar.gz format to be emailed''' #Tar file and add it to message - with tarfile.open(f"{self.reports_path}/gecko_{self.utc_date}.tar.gz", "w:gz") as tar: - tar.add(f"{self.reports_path}", arcname=os.path.basename(f"{self.reports_path}")) + #with tarfile.open(f"{self.reports_path}/gecko_{self.utc_date}.tar.gz", "w:gz") as tar: + # tar.add(f"{self.reports_path}", arcname=os.path.basename(f"{self.reports_path}")) + + filename = f"{self.reports_path}/gecko_{self.utc_date}" + filename = filename.replace(" ", "_").replace('+','') + self.tar_filename = filename.replace(':', '').replace('.', '_') + ".tar.gz" + with tarfile.open(self.tar_filename, "w:gz") as tar: + for root, dirs, files in os.walk(self.reports_path): + for file in files: + + # Only include files containing utc_time + if self.utc_time in file or ".log" in file: + full_path = os.path.join(root, file) + + # Keep relative structure inside tar + arcname = os.path.relpath(full_path, self.reports_path) + + tar.add(full_path, arcname=arcname) def grab_science_image(self): '''Grabs most recent science image(s) to include in triage''' @@ -352,17 +370,16 @@ def send_report(self): ) #tar.gz file next - tar_file = f"{self.reports_path}/gecko_{self.utc_date}.tar.gz" - with open(tar_file, 'rb') as f: + with open(self.tar_filename, 'rb') as f: msg.add_attachment( f.read(), maintype='application', subtype='gzip', - filename=os.path.basename(tar_file) + filename=os.path.basename(self.tar_filename) ) # Attach PNG images recursively from the reports_path - image_files = glob.glob(os.path.join(self.reports_path, '**', '*.png'), recursive=True) + image_files = glob.glob(os.path.join(self.reports_path, '**', f'*{self.utc_time}.png'), recursive=True) #for file in image_files: # with open(file, 'rb') as fp: # img_data = fp.read() From 704b45f36a461a7273ee8eea3d5f6dd90f393eb1 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 13:21:02 -0800 Subject: [PATCH 06/13] executable changes --- gecko | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/gecko b/gecko index 94797e8..a031271 100755 --- a/gecko +++ b/gecko @@ -75,8 +75,7 @@ def run_triage(): if not args.initialize: user_message = f"USER: {args.user}\nMESSAGE: {args.message}" gecko = Triagetools(config=CONFIG_PATH, message=user_message) - print(f"Running triage workflow for user: {args.user}\n" - "with the message: {args.message}\n") + print(f"Running triage workflow for user: {args.user}\nwith the message: {args.message}\n") gecko.gather_system_info() gecko.gather_logs() gecko.comb_logs() @@ -84,14 +83,14 @@ def run_triage(): gecko.grab_science_image() gecko.compress_report() - #if gecko.email_alerts: - # gecko.send_report() + if gecko.email_alerts: + gecko.send_report() print("\nTriage workflow complete!") else: print("\n\nYou need to include a user and a message in your report.") - print("example: ./gecko -u 'Steven.B' -m 'One of the channels looked weird " - "and I was unable to get any more data'") + print("example: \n./gecko -u 'Steven.B' -m 'One of the channels looked weird " + "and I was unable to get any more data'\n\n") if __name__ == "__main__": From e90f1732137646c59709956c06ab7d100c902174 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:14:51 -0800 Subject: [PATCH 07/13] readme update --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 48ffe24..a2dc365 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,41 @@ -# python-package-template -This is a template on how to package a simple Python project +# Gecko Triage tool +## What is Gecko? +Gecko is a command-line triage and diagnostics tool designed to help operators and engineers quickly collect useful debugging information when an instrument or system encounters a problem. + +Instead of manually gathering logs, screenshots, and science images, Gecko automates the process and produces a single packaged report that can be reviewed or shared with the development and operations teams. + +Gecko is especially useful during: + +- Instrument failures or unexpected behavior +- Software crashes or lockups +- Data acquisition issues +- Hardware communication problems +- On-sky observing anomalies + +--- + +### What Gecko Can Collect + +When run in triage mode, Gecko can automatically gather: + +- System and application log files +- Instrument-specific telemetry or diagnostic outputs +- Screenshots of the current system state +- Science images or recent exposure data (if configured) +- A compressed report bundle for archiving or emailing + +--- + +### Why Use Gecko? + +Gecko provides a consistent and repeatable way to capture critical debugging context at the moment an issue occurs. This reduces downtime and helps teams diagnose problems faster, without requiring users to manually locate and send multiple files. + +Once initialized, generating a triage report is as simple as: + +```bash +./gecko -u "your.name" -m "Description of the issue" +''' ## Table of Contents 1. Installation From fc0bd20053ecd5e65319515cdce022db21ad9be8 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:17:53 -0800 Subject: [PATCH 08/13] readme update --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2dc365..7a146a8 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ Once initialized, generating a triage report is as simple as: ```bash ./gecko -u "your.name" -m "Description of the issue" ''' -## Table of Contents + +### Table of Contents 1. Installation 2. Setting Up Your Package @@ -44,7 +45,7 @@ Once initialized, generating a triage report is as simple as: 4. Building Your Package 5. Publishing to PyPI -## Installation +### Installation To install the package in editable mode (ideal for development), follow these steps: From 12cc68fb0ecc713007c63292efe37129d65e0c7b Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:18:47 -0800 Subject: [PATCH 09/13] readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a146a8..b6682bd 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Once initialized, generating a triage report is as simple as: ```bash ./gecko -u "your.name" -m "Description of the issue" -''' +``` -### Table of Contents +## Table of Contents 1. Installation 2. Setting Up Your Package From f523ac1990a3d125d1f83f6f00a932190af9c612 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:39:33 -0800 Subject: [PATCH 10/13] readme update --- README.md | 2 ++ triage_package/INIT_GUIDE.md | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 triage_package/INIT_GUIDE.md diff --git a/README.md b/README.md index b6682bd..7e19298 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ python3 gecko -init (or ./gecko -init) ``` +[Initialization Guide](INIT_GUIDE.md) + ### Make Executable avaiable anywhere in the system Symlink the executable file to bin: diff --git a/triage_package/INIT_GUIDE.md b/triage_package/INIT_GUIDE.md new file mode 100644 index 0000000..6884053 --- /dev/null +++ b/triage_package/INIT_GUIDE.md @@ -0,0 +1,55 @@ +Gecko Initialization Guide (`-init`) +==================================== + +Gecko requires a configuration file before it can generate full triage reports. +The initialization process (`-init`) walks the user through this configuration step-by-step by asking questions and saving responses into an `.ini` file. + +This guide explains what happens during initialization, what information is required, and what each section of the config file controls. + +Overview: What Does `-init` Do? +------------------------------- + +Running:: + + ./gecko -init + +will prompt you with questions that need to be answered in order to create an accurate config/ini file for your reporting. + +Example Inputs +-------------- + +Each section will include a block of text with example inputs to simplify the configuration process. + +Important Sections +------------------ + +Primary Section +^^^^^^^^^^^^^^^ + +Boolean Options:: + + email_alerts: false + +Who will receive the report email:: + + recipient_email: eng@observatory.edu + +Required Directories (must exist):: + + report_path: /home/user/dir/reports + logs_dir: /data/latest/logs/ + science_dir: /data/latest/ + +Notes +----- + +- All paths listed in the configuration must already exist on the system. Gecko will not create them automatically. +- Ensure the email address is valid if email alerts are enabled. +- Boolean options accept only ``true`` or ``false``. +- After initialization, you can manually edit the `.ini` file if needed. + +Summary +------- + +The ``-init`` process is designed to simplify setup by guiding the user through necessary configurations. +Once completed, Gecko can generate triage reports accurately using the saved configuration. From 512fa56ad6fa2d651a646076fa4318a32a24b456 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:40:26 -0800 Subject: [PATCH 11/13] readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e19298..d4a30b6 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ python3 gecko -init (or ./gecko -init) ``` -[Initialization Guide](INIT_GUIDE.md) +[Initialization Guide](triage_package/INIT_GUIDE.md) ### Make Executable avaiable anywhere in the system From a2a5cfd746ed291c9dd85ad19e388ba3fecc3835 Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 15:42:29 -0800 Subject: [PATCH 12/13] readme update --- triage_package/INIT_GUIDE.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/triage_package/INIT_GUIDE.md b/triage_package/INIT_GUIDE.md index 6884053..2960b9e 100644 --- a/triage_package/INIT_GUIDE.md +++ b/triage_package/INIT_GUIDE.md @@ -23,8 +23,7 @@ Each section will include a block of text with example inputs to simplify the co Important Sections ------------------ -Primary Section -^^^^^^^^^^^^^^^ +***Primary Sections*** Boolean Options:: From c17b8d630e1157ac6f608131640416e5e1db9d2c Mon Sep 17 00:00:00 2001 From: Elijah A-B Date: Fri, 6 Feb 2026 16:30:43 -0800 Subject: [PATCH 13/13] slight change to sender_email variable --- triage_package/triage.ini | 4 ++-- triage_package/triage_tool.py | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/triage_package/triage.ini b/triage_package/triage.ini index a321c66..8006a19 100644 --- a/triage_package/triage.ini +++ b/triage_package/triage.ini @@ -30,7 +30,7 @@ help_text = time_pattern: ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?) email_alerts: false recipient_email: eng@observatory.edu -# sender_email: hello@gmail.com + machine_name: VenusServer # sender_password: password @@ -38,7 +38,7 @@ report_path = time_pattern = ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?) email_alerts = recipient_email = -#sender_email = +machine_name = #sender_password = [Machine] diff --git a/triage_package/triage_tool.py b/triage_package/triage_tool.py index c27a4da..d19784c 100755 --- a/triage_package/triage_tool.py +++ b/triage_package/triage_tool.py @@ -109,7 +109,7 @@ def load_config(self, config): self.email_alerts = self.config.getboolean("Report", "email_alerts") if self.email_alerts: self.target_email = self.config["Report"]["recipient_email"] - #self.sender_email = self.config["Report"]["sender_email"] + self.machine_name = self.config["Report"]["machine_name"] #self.sender_password = self.config["Report"]["sender_password"] self.r_path = self.config["Report"]["report_path"] self.log_dir = self.config["Logs"]["logs_dir"] @@ -354,11 +354,12 @@ def send_report(self): # Create email msg = EmailMessage() msg['Subject'] = f'Gecko Report {self.utc_date}' - msg['From'] = self.sender_email # replace with actual sender + msg['From'] = self.machine_name # replace with actual sender msg['To'] = self.target_email # can be comma-separated string or list # Email body - msg.set_content("Please see attached report images.") + body = f"\n{self.message}\n\n Unzip tar file to see logs, pngs, etc\n" + msg.set_content(body) #.txt file first with open(self.report_name, 'rb') as f: @@ -379,7 +380,7 @@ def send_report(self): ) # Attach PNG images recursively from the reports_path - image_files = glob.glob(os.path.join(self.reports_path, '**', f'*{self.utc_time}.png'), recursive=True) + #image_files = glob.glob(os.path.join(self.reports_path, '**', f'*{self.utc_time}.png'), recursive=True) #for file in image_files: # with open(file, 'rb') as fp: # img_data = fp.read() @@ -396,14 +397,14 @@ def send_report(self): # smtp.send_message(msg) #print(f"Report sent to {self.target_email}") - for file in image_files: - with open(file, "rb") as fp: - msg.add_attachment( - fp.read(), - maintype="image", - subtype="png", - filename=os.path.basename(file) - ) + #for file in image_files: + # with open(file, "rb") as fp: + # msg.add_attachment( + # fp.read(), + # maintype="image", + # subtype="png", + # filename=os.path.basename(file) + # ) # Send using local sendmail instead of SMTP try: