vigil/main.py
2025-10-15 08:14:18 +01:00

148 lines
4.5 KiB
Python

# _ __________________
# | | / / _/ ____/ _/ /
# | | / // // / __ / // /
# | |/ // // /_/ // // /___
# |___/___/\____/___/_____/
# © Uthmn 2025 under MIT license
import time
import services.apt
import services.mail
from socket import gethostname
from dotenv import load_dotenv
from os import getenv
from os.path import dirname, join
import typer
# Load environment variables from .env file
dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)
# FIXME: Error handling
# FIXME: Add json email list that if not added will use parameters
# FIXME: Handle no updates available
app = typer.Typer()
@app.command()
def serve():
"""
Checks for updates and emails hourly for security, daily for general as a daemon.
"""
@app.command()
def now(receiver_email: str):
"""
Checks for apt upgrades and emails them then exits.
"""
generate_email(receiver_email)
def generate_email(receiver_email: str):
services.apt.require_root()
if not services.apt.detect_apt():
print("Apt not found on this system.")
exit(1)
updates = services.apt.check_updates()
# Get how many security updates are available
security_updates = 0
for package in updates:
if updates[package]["security"]:
security_updates += 1
# Get how many total updates are available
total_updates = len(updates)
# Get how many general updates are available
general_updates = total_updates - security_updates
# Check if there are any updates at all
if total_updates == 0:
print("No updates available.")
return
# Get system hostname
hostname = gethostname()
# Create email subject
subject = f"{hostname}> {security_updates} security updates, {general_updates} general updates available"
# Build security updates section
security_chunks = ""
for package in updates:
if not updates[package]["security"]:
continue
chunk = f'''
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">{package}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["installed_version"]}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["latest_version"]}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["repo"]}</td>
</tr>
'''
security_chunks += chunk
security = ""
if security_updates > 0:
security = f'''
<h1 style="font-family: Arial, sans-serif;">Security</h1>
<table style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;">
<tr>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Package</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Installed</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Latest</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Repository</th>
</tr>
{security_chunks}
</table>
'''
# Build general updates section
general_chunks = ""
for package in updates:
if updates[package]["security"]:
continue
chunk = f'''
<tr>
<td style="border: 1px solid #ddd; padding: 8px;">{package}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["installed_version"]}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["latest_version"]}</td>
<td style="border: 1px solid #ddd; padding: 8px;">{updates[package]["repo"]}</td>
</tr>
'''
general_chunks += chunk
general = ""
if general_updates > 0:
general = f'''
<h1 style="font-family: Arial, sans-serif;">General</h1>
<table style="border-collapse: collapse; width: 100%; font-family: Arial, sans-serif;">
<tr>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Package</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Installed</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Latest</th>
<th style="border: 1px solid #ddd; background-color: #f2f2f2; padding: 8px;">Repository</th>
</tr>
{general_chunks}
</table>
'''
html = security + general
services.mail.send_email(receiver_email, subject, html)
@app.callback()
def callback():
"""
Checks apt for upgrades and emails them
"""
if __name__ == "__main__":
app()