diff --git a/Containerfile.builder b/Containerfile.builder index e6af03d..df05bcc 100644 --- a/Containerfile.builder +++ b/Containerfile.builder @@ -22,5 +22,3 @@ RUN dnf install \ FROM builder AS rpm RUN make build-rpm - - diff --git a/README.md b/README.md index 6053bc0..c5bff12 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Small update program written in python intended for use in Universal Blue, execu Includes systemd timers and services for auto update -dependencies (fedora): ```sudo dnf install python3-notify2 python3-psutil``` +dependencies (fedora): ```sudo dnf install python3-psutil libnotify``` # Usage @@ -45,11 +45,11 @@ options: ## Location valid config paths (in order of priority) -```"$HOME"/.config/ublue-update/ublue-update.conf``` +```"$HOME"/.config/ublue-update/ublue-update.toml``` -```/etc/ublue-update/ublue-update.conf``` +```/etc/ublue-update/ublue-update.toml``` -```/usr/etc/ublue-update/ublue-update.conf``` +```/usr/etc/ublue-update/ublue-update.toml``` ## Config Variables diff --git a/files/usr/lib/systemd/user/ublue-update.service b/files/usr/lib/systemd/user/ublue-update.service index 66b7076..7fc8b4d 100644 --- a/files/usr/lib/systemd/user/ublue-update.service +++ b/files/usr/lib/systemd/user/ublue-update.service @@ -1,6 +1,10 @@ [Unit] -Description=auto-update oneshot service +Description=Universal Blue Update Oneshot Service [Service] +Restart=on-failure +RestartSec=1h Type=oneshot ExecStart=/usr/bin/ublue-update + + diff --git a/files/usr/lib/systemd/user/ublue-update.timer b/files/usr/lib/systemd/user/ublue-update.timer index 4dc87a0..4de3904 100644 --- a/files/usr/lib/systemd/user/ublue-update.timer +++ b/files/usr/lib/systemd/user/ublue-update.timer @@ -1,10 +1,11 @@ [Unit] -Description=auto update system timer for uBlue +Description=Auto Update System Timer For Universal Blue Wants=network-online.target [Timer] -OnBootSec=15min -OnUnitInactiveSec=6h +RandomizedDelaySec=10m +OnBootSec=2m +OnCalendar=*-*-* 4:00:00 Persistent=true [Install] diff --git a/src/ublue_update/cli.py b/src/ublue_update/cli.py index 6aff7c2..6b4373e 100644 --- a/src/ublue_update/cli.py +++ b/src/ublue_update/cli.py @@ -5,21 +5,36 @@ import tomllib import argparse -from ublue_update.notification_manager import NotificationManager from ublue_update.update_checks.system import system_update_check +def notify(title: str, body: str, actions: list = [], expire_time: int = 0): + args = [ + "/usr/bin/notify-send", + title, + body, + "--app-name=Univeral Blue Updater", + "--icon=software-update-available-symbolic", + ] + if expire_time != 0: + args.append(f"--expire-time={expire_time}") + if actions != []: + for action in actions: + args.append(f"--action={action}") + out = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + return out + + def ask_for_updates(): - update_notif = notification_manager.notification( + out = notify( "System Updater", "Update available, but system checks failed. Update now?", + ["universal-blue-update-confirm=Confirm"], + 15000, ) - update_notif.add_action( - "universal-blue-update-confirm", - "Confirm", - lambda: run_updates(), - ) - update_notif.show(15) + # if the user has confirmed + if "universal-blue-update-confirm" in out.stdout.decode("utf-8"): + run_updates() def check_for_updates(checks_failed: bool) -> bool: @@ -136,6 +151,8 @@ def load_value(key, value): def run_updates(): root_dir = "/etc/ublue-update.d/" + log.info("Running system update") + for root, dirs, files in os.walk(root_dir): for file in files: full_path = root_dir + str(file) @@ -150,17 +167,17 @@ def run_updates(): log.info(f"{full_path} returned error code: {out.returncode}") log.info(f"Program output: \n {out.stdout}") if dbus_notify: - notification_manager.notification( + notify( "System Updater", f"Error in update script: {file}, check logs for more info", - ).show(5) + ) else: log.info(f"could not execute file {full_path}") if dbus_notify: - notification_manager.notification( + notify( "System Updater", "System update complete, reboot for changes to take effect", - ).show(5) + ) log.info("System update complete") os._exit(0) @@ -178,15 +195,6 @@ def run_updates(): ) log = logging.getLogger(__name__) -notification_manager = None - -# Sometimes the system doesn't have a running dbus session or a notification daemon -try: - notification_manager = NotificationManager("Universal Blue Updater") -except Exception: - dbus_notify = False - - def main(): # setup argparse @@ -229,9 +237,8 @@ def main(): # system checks passed log.info("System passed all update checks") if dbus_notify: - notification_manager.notification( + notify( "System Updater", "System passed checks, updating ...", - ).show(5) - + ) run_updates() diff --git a/src/ublue_update/notification_manager.py b/src/ublue_update/notification_manager.py deleted file mode 100644 index 6bff11f..0000000 --- a/src/ublue_update/notification_manager.py +++ /dev/null @@ -1,78 +0,0 @@ -import dbus -from typing import Callable -from dbus.mainloop.glib import DBusGMainLoop -from gi.repository import GLib - - -class NotificationObject: - """Holds all of the data in a notification""" - - def __init__(self, notification_manager, app_name, icon="", title="", body=""): - self.notification_manager = notification_manager - self.app_name = app_name - self.id = 0 - self.icon = icon - self.title = title - self.body = body - self.actions = {} - self.hints = {} - - def add_action(self, key: str, text: str, handler: Callable): - self.actions.update({key: {"text": text, "handler": handler}}) - - def add_hint(self, hint: dict): - self.hints.update(hint) - - def show(self, timeout_sec: float): - actions = self.notification_manager.get_action_list(self.actions) - self.id = self.notification_manager.notify_interface.Notify( - self.app_name, - self.id, - self.icon, - self.title, - self.body, - actions, - self.hints, - timeout_sec * 1000, - ) - self.notification_manager.notifications.update({self.id: self}) - if self.actions: - self.notification_manager.loop.run() - - -class NotificationManager: - """Manages DBus notifications and action dispatching""" - - def __init__(self, app_name: str): - item = "org.freedesktop.Notifications" - path = "/" + item.replace(".", "/") - self.dbus_loop = DBusGMainLoop() - self.loop = GLib.MainLoop() - self._bus = dbus.SessionBus(mainloop=self.dbus_loop) - self._bus.add_signal_receiver(self._on_action, "ActionInvoked", item) - self._bus.add_signal_receiver(self._on_closed, "NotificationClosed", item) - self._app_name = app_name - self.notifications = {} - self.notify_interface = dbus.Interface(self._bus.get_object(item, path), item) - - def get_action_list(self, actions: dict): - dbus_actions = [] - for action in actions: - action_dict = actions.get(action) - dbus_actions.append(action) - dbus_actions.append(action_dict["text"]) - return dbus_actions - - def _on_action(self, id: int, action_key: str): - notification = self.notifications.get(id) - if notification: - triggered_action = notification.actions.get(action_key) - triggered_action["handler"]() - self.loop.quit() - - def _on_closed(self, id: int, reason: str): - if self.loop.is_running(): - self.loop.quit() - - def notification(self, title: str, body: str, icon: str = ""): - return NotificationObject(self, self._app_name, icon, title, body) diff --git a/ublue-update.spec.rpkg b/ublue-update.spec.rpkg index 78ed9ea..9197acf 100644 --- a/ublue-update.spec.rpkg +++ b/ublue-update.spec.rpkg @@ -28,6 +28,7 @@ BuildRequires: pyproject-rpm-macros BuildRequires: python-setuptools_scm BuildRequires: python-wheel Requires: skopeo +Requires: libnotify %global sub_name %{lua:t=string.gsub(rpm.expand("%{NAME}"), "^ublue%-", ""); print(t)}