# _ __________________
# | | / / _/ ____/ _/ /
# | | / // // / __ / // /
# | |/ // // /_/ // // /___
# |___/___/\____/___/_____/
# © Uthmn 2025 under MIT license
import time
import services.apt
import services.mail
from socket import gethostname
import json
from os.path import dirname, join, exists
import typer
# FIXME: Error handling
app = typer.Typer()
@app.command()
def serve(receiver_email: str = None, check_interval: int = 24):
"""
Checks for updates at a fixed interval and sends email at that interval as a daemon. Doesn't email without updates.
Accepts check_interval as a parameter in hours (default 24)
"""
@app.command()
def now(receiver_email: str = None):
if not receiver_email:
receiver_email = [email.strip() for email in receiver_email.split(",")]
"""
Checks for apt upgrades and emails them then exits.
"""
generate_email(receiver_email)
def generate_email(receiver_email: list):
services.apt.require_root()
if not services.apt.detect_apt():
print("Apt not found on this system.")
exit(1)
updates = services.apt.check_updates()
if exists(join(dirname(__file__), "users.json")):
with open(join(dirname(__file__), "users.json"), "r") as f:
users = json.load(f)
receiver_email = users
else:
if receiver_email is None:
print("No email address provided.")
exit(1)
# 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'''
| {package} |
{updates[package]["installed_version"]} |
{updates[package]["latest_version"]} |
{updates[package]["repo"]} |
'''
security_chunks += chunk
security = ""
if security_updates > 0:
security = f'''
Security
| Package |
Installed |
Latest |
Repository |
{security_chunks}
'''
# Build general updates section
general_chunks = ""
for package in updates:
if updates[package]["security"]:
continue
chunk = f'''
| {package} |
{updates[package]["installed_version"]} |
{updates[package]["latest_version"]} |
{updates[package]["repo"]} |
'''
general_chunks += chunk
general = ""
if general_updates > 0:
general = f'''
General
| Package |
Installed |
Latest |
Repository |
{general_chunks}
'''
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()