#!/usr/bin/env python3 import os import pydbus import sys import time import urllib.request DBUS_NAME = 'com.witekio.update1' DBUS_OBJECT = '/com/witekio/update1' DBUS_INTERFACE = 'com.witekio.update1.Manager' AUTOUPDATE_INSTALLED = "/var/lib/autoupdate/installed" # used to keep track of which version got installed AUTOUPDATE_PENDING = "/var/lib/autoupdate/pending" # will be moved to _INSTALLED after confirmation AUTOUPDATE_ACTIVE_PARTITIONS = "/var/lib/autoupdate/latest-active-partitions" # used to detect rollback after reboot def dbus_init(): bus = pydbus.SystemBus() obj = bus.get(DBUS_NAME, DBUS_OBJECT) itf = obj[DBUS_INTERFACE] return obj, itf def dbus_install(filename): DBUS_INTERFACE.BeginUpdate() try: DBUS_INTERFACE.InstallLocalFile(filename) DBUS_INTERFACE.SubmitUpdate() except Exception: DBUS_INTERFACE.AbortUpdate() def wait_for_network(url): while True: try: print(f"wait_for_network: GET {url}") urllib.request.urlopen(f"{url}").read() # read() leads to closing the socket break except Exception: # Retry in 5 s time.sleep(5) def get_active_partitions(): """Get a determinist footprint of active partitions""" dir_active = "/dev/disk/partitions/active" partitions = [os.readlink(f"{dir_active}/{f}") for f in os.listdir(dir_active)] partitions.sort() return ' '.join(partitions) def complete_pending_install(url): """Complete a pending installation and report to 'url'""" if os.path.exists(AUTOUPDATE_PENDING): # Let's find out if the update was successful or if we rolled back with open(AUTOUPDATE_ACTIVE_PARTITIONS) as fd: if get_active_partitions().strip() == fd.read().strip(): # No change. That's a rollback. urllib.request.urlopen(f"{url}/update/rollback", data=b'') # data= implies method POST os.unlink(AUTOUPDATE_PENDING) else: # Partitions changed. That means a successful update. urllib.request.urlopen(f"{url}/update/ok", data=b'') # data= implies method POST os.rename(AUTOUPDATE_PENDING, AUTOUPDATE_INSTALLED) # Main host = sys.argv[1] port = sys.argv[2] URL_BASE = f"http://{host}:{port}" DBUS_OBJECT, DBUS_INTERFACE = dbus_init() wait_for_network(URL_BASE) if DBUS_OBJECT.State == 'under-test': DBUS_INTERFACE.ConfirmUpdate() complete_pending_install(URL_BASE) while True: try: # Get latest revision to be installed print(f"GET {URL_BASE}/applicable") applicable = urllib.request.urlopen(f"{URL_BASE}/applicable").read() applicable = applicable.decode('utf-8').strip() try: # Get revision of locally installed package with open(AUTOUPDATE_INSTALLED) as fd: installed = fd.read().strip() except Exception: installed = None print(f"installed={installed}, applicable={applicable}") if installed is None or installed != applicable: # A new package is available. Download and install it. print(f"GET {URL_BASE}/{applicable}") urllib.request.urlretrieve(f"{URL_BASE}/{applicable}", "/tmp/autoupdate.pkg") # Call install via d-bus dbus_install("/tmp/autoupdate.pkg") # Save active partitions with open(AUTOUPDATE_ACTIVE_PARTITIONS, 'w') as fd: fd.write(get_active_partitions()+"\n") # Save the revision that we installed with open(AUTOUPDATE_PENDING, 'w') as fd: fd.write(f"{applicable}\n") # Exit normally. The systemd config should trigger a reboot. break except Exception as e: print(f"Failed: {e}") time.sleep(20)