Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 12 additions & 18 deletions triage_package/triage.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,27 @@ help_text =
This section will ask for details on the reports. If you would not like email notifications, you can just provide a reports path.
(EXAMPLES)
report_path: /home/user/dir/reports
compression type: zip or tar
time_pattern: ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)
email_alerts: false
email_alerts: true
machine_email: (true = send email as machine, false = provide email information)
recipient_email: eng@observatory.edu
# sender_email: hello@gmail.com
# sender_password: password
sender_email: hello@gmail.com
sender_password: password


report_path =
compression =
time_pattern = ^(\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)
email_alerts =
help_text =
If you ansered 0, no, false or off for email alerts, no answers for the email prompts will be relavant.

machine_email =
recipient_email =
machine_name =
#sender_email =
#sender_password =
sender_email =
sender_password =

[Machine]
help_text =
Expand All @@ -52,16 +59,3 @@ 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


#host =
#password =
#vnc_sessions =
163 changes: 94 additions & 69 deletions triage_package/triage_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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.zip_filename = ""
self.filename = ""

# Create a ConfigParser object
self.config = configparser.ConfigParser()
Expand Down Expand Up @@ -105,11 +105,18 @@ def load_config(self, config):
sys.exit(0)

#Report Section(report file name and current UTCdate folder)
self.email_alerts = self.config.getboolean("Report", "email_alerts")
self.email_alerts = self.config.getboolean("Report", "Send email alerts?")
if self.email_alerts:
self.machine_email = self.config.getboolean("Report","Send email from machine?")
if not self.machine_email:
self.sender_email = self.config["Report"]["sender_email"]
self.sender_password = self.config["Report"]["sender_password"]
else:
self.machine_name = self.config["Report"]["machine_name"]
self.target_email = self.config["Report"]["recipient_email"]
self.machine_name = self.config["Report"]["machine_name"]
#self.sender_password = self.config["Report"]["sender_password"]
self.include_images = self.config.getboolean("Report","Include images in email?")
self.attach_report = self.config.getboolean("Report","Include report in email?")
self.compression = (self.config["Reports"]["compression"]).lower()
self.r_path = self.config["Report"]["report_path"]
self.log_dir = self.config["Logs"]["logs_dir"]
self.science_dir = self.config["Logs"]["science_dir"]
Expand All @@ -133,12 +140,6 @@ def load_config(self, config):
self.os = self.config["System"]["os"]
self.os_version = self.config["System"]["os_version"]

#VNC information
#self.host = self.config["VNC"]["host"]
#self.password = self.config["VNC"]["password"]
#temp_sessions = self.config["VNC"]["vnc_sessions"]
#self.vnc_sessions = [int(num.strip()) for num in temp_sessions.split(',')]

#Put main message into the file
with open(self.report_name, 'w', encoding='utf-8') as report_file:
report_file.write("=========Reported Error From User==========\n")
Expand Down Expand Up @@ -294,24 +295,33 @@ def comb_logs(self):
def compress_report(self):
"""Compresses report files into a .zip format to be emailed"""

filename = f"{self.reports_path}/gecko_{self.utc_date}"
filename = filename.replace(" ", "_").replace("+", "")
self.zip_filename = filename.replace(":", "").replace(".", "_") + ".zip"
# 1. Define the base filename
base_name = f"{self.reports_path}/gecko_{self.utc_date}".replace(" ", "_").replace("+", "")
clean_name = base_name.replace(":", "").replace(".", "_")

with zipfile.ZipFile(self.zip_filename, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(self.reports_path): # pylint: disable = W0612
for file in files:
# 2. Handle the specific compression logic
if self.compression == "tar":
self.filename = clean_name + ".tar.gz"
context_manager = tarfile.open(self.filename, "w:gz")
else:
self.filename = clean_name + ".zip"
context_manager = zipfile.ZipFile(self.filename, "w", compression=zipfile.ZIP_DEFLATED)

# Only include files containing utc_time OR log files
# 3. Perform the actual archiving
with context_manager as archive:
for root, _, files in os.walk(self.reports_path):
for file in files:
if self.utc_time in file or file.endswith(".log"):
full_path = os.path.join(root, file)

# Keep relative structure inside zip
arcname = os.path.relpath(full_path, self.reports_path)

# Use duck-typing: both objects have different methods for adding files
if isinstance(archive, tarfile.TarFile):
archive.add(full_path, arcname=arcname)
else:
archive.write(full_path, arcname=arcname)

zipf.write(full_path, arcname=arcname)

print(f"Created ZIP file: {self.zip_filename}")
print(f"Created archive: {self.filename}")

def grab_science_image(self):
'''Grabs most recent science image(s) to include in triage'''
Expand Down Expand Up @@ -355,15 +365,15 @@ def send_report(self):
msg['Subject'] = f'Gecko Report {self.utc_date}'
msg['From'] = self.machine_name # replace with actual sender
msg['To'] = self.target_email # can be comma-separated string or list
zip_path = os.path.join(self.reports_path, self.zip_filename)
compressed_file_path = os.path.join(self.reports_path, self.filename)

# Email body
body = (
f"{self.message}\n\n"
"To view logs and images:\n"
" 1. Download the attached .zip file\n"
" 1. Download the attached compressed file\n"
" 2. Extract it\n\n"
f"Compressed report location:\n{zip_path}"
f"Compressed report location:\n{compressed_file_path}"
)
msg.set_content(body)

Expand All @@ -376,53 +386,68 @@ def send_report(self):
filename=os.path.basename(self.report_name)
)

#.zip file next
#with open(self.zip_filename, 'rb') as f:
# msg.add_attachment(
# f.read(),
# maintype='application',
# subtype='gzip',
# filename=os.path.basename(self.zip_filename)
# )
#compressed file next
# Use == for string comparison; 'is' checks object identity, which can fail for strings
if self.compression == "tar":
subtype = 'gzip' # Standard for .tar.gz
else:
subtype = 'zip' # Standard for .zip files

if self.attach_report:
with open(self.filename, 'rb') as f:
file_data = f.read()
msg.add_attachment(
file_data,
maintype='application',
subtype=subtype,
filename=os.path.basename(self.filename)
)

# 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)
#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)
#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}")
if self.include_images:
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()
filename = os.path.basename(file)
msg.add_attachment(img_data, maintype='image', subtype='png', filename=filename)

# Connect to email provider SMTP to send
if not self.machine_email:
# Map of domains to their SMTP settings
smtp_settings = {
"gmail.com": ("smtp.gmail.com", 587),
"yahoo.com": ("smtp.mail.yahoo.com", 587),
"outlook.com": ("smtp-mail.outlook.com", 587),
"hotmail.com": ("smtp-mail.outlook.com", 587)
}

# Extract domain from sender email (e.g., 'user@gmail.com' -> 'gmail.com')
domain = self.sender_email.split('@')[-1].lower()

# Get settings or default to Outlook if not found
server_host, server_port = smtp_settings.get(domain, ("smtp-mail.outlook.com", 587))

try:
# Most providers now prefer SMTP + starttls() on port 587
with smtplib.SMTP(server_host, server_port) as smtp:
smtp.starttls() # Upgrade the connection to secure
smtp.login(self.sender_email, self.sender_password) # Must be an App Password
smtp.send_message(msg)
print(f"Report successfully sent to {self.target_email}")
except Exception as e:
print(f"Failed to send email: {e}")
else:
try:
subprocess.run(
["/usr/sbin/sendmail", "-t", "-oi"],
input=msg.as_bytes(),
check=True
)
print(f"Report sent to {self.target_email}")

except Exception as e: #pylint: disable=W0718
print("Failed to send report:", e)
except Exception as e: #pylint: disable=W0718
print("Failed to send report:", e)

def cleanup_reports_dir(self):
"""Clean up and rid of all files that are not a .zip file"""
Expand Down