@@ -28,12 +28,32 @@ from shlex import quote
2828import xml .etree .ElementTree as ET
2929
3030# Constants
31- VERSION = "0.1.14 "
31+ VERSION = "0.1.15 "
3232ZYPPER_PID_FILE = "/run/zypp.pid"
3333VALID_CMD = ["dup" , "run" , "rollback" ]
3434VALID_OPT = ["--reboot" , "--apply" , "--shell" , "--continue" , "--no-verify" , \
3535 "--interactive" , "--debug" , "--help" , "--version" ]
3636
37+ # Required programs / dependecies
38+ REQUIRED_DEP = ["zypper" , "snapper" , "btrfs" , "echo" , "ps" , "sed" , "awk" , "bash" , "sort" , \
39+ "env" , "chroot" , "mount" , "umount" , "rmdir" , "findmnt" , "systemd-nspawn" , \
40+ "systemctl" , "machinectl" , "systemd-analyze" ]
41+
42+ # The exit code of these programs (if it exists) in addition to the required programs
43+ # will be checked pre/post each transaction/update
44+ CHK_PROGRAMS = [
45+ "Xorg" ,
46+ "Xwayland" ,
47+ "pipewire" ,
48+ "wireplumber" ,
49+ "firefox" ,
50+ "thunderbird" ,
51+ "gdm" ,
52+ "gnome-shell" ,
53+ "gnome-control-center" ,
54+ # TODO: add the display manager, graphical shell, and settings app binary for other DE/WMs
55+ ]
56+
3757# Command help/usage info
3858help_text = """
3959Usage: atomic-update [options] command
@@ -97,8 +117,25 @@ def get_atomic_snap(snapper_root_config, status):
97117 except :
98118 pass
99119
100- # Function to verify snapshot by booting it up as a container
101- def verify_snapshot ():
120+ # Function to verify snapshot's ability to run important programs -
121+ # acts as a basic check for missing and incompatible libraries
122+ def verify_programs (TMP_MOUNT_DIR ):
123+ failed_programs = []
124+ programs = REQUIRED_DEP + CHK_PROGRAMS
125+ logging .debug (f"Verifying programs: { ', ' .join (programs )} " )
126+ for program in programs :
127+ version_str = "-version" if program in ["Xorg" , "Xwayland" ] else "--version"
128+ command = f"chroot { TMP_MOUNT_DIR } bash -c '" \
129+ f"command -v { program } || exit 0 && sudo -u nobody { program } { version_str } " \
130+ f"' > /dev/null 2>&1"
131+ out , ret = shell_exec (command )
132+ if ret != 0 :
133+ failed_programs .append (program )
134+ logging .debug (f"Failed programs: { ', ' .join (failed_programs )} " )
135+ return failed_programs
136+
137+ # Function to verify snapshot's systemd units by booting it up as a container
138+ def verify_units ():
102139 logging .debug ("Booting container" )
103140 cmd = ["systemd-nspawn" , "--directory" , TMP_MOUNT_DIR , "--ephemeral" , "--boot" , \
104141 "systemd.mask=local-fs.target" , "systemd.mask=auditd.service" , "systemd.mask=kdump.service" ]
@@ -305,14 +342,11 @@ if os.getuid() != 0:
305342 sys .exit (2 )
306343
307344# Bail out if required dependecies are not available
308- programs = ["zypper" , "snapper" , "btrfs" , "echo" , "ps" , "sed" , "awk" , "bash" , "sort" , \
309- "env" , "chroot" , "mount" , "umount" , "rmdir" , "findmnt" , "systemd-nspawn" , \
310- "systemctl" , "machinectl" , "systemd-analyze" ]
311- for program in programs :
345+ for program in REQUIRED_DEP :
312346 if not shell_exec (f"command -v { program } " )[0 ]:
313347 logging .error (f"Bailing out, missing required dependecy { program !r} in PATH ({ os .environ .get ('PATH' )} ) " \
314348 f"for user { os .environ .get ('USER' )!r} . The following programs " \
315- f"are required for atomic-update to function: { ', ' .join (programs )} "
349+ f"are required for atomic-update to function: { ', ' .join (REQUIRED_DEP )} "
316350 )
317351 sys .exit (3 )
318352
@@ -403,7 +437,8 @@ chroot {TMP_MOUNT_DIR} mount -a -O no_netdev;
403437 # verify snapshot prior to performing update
404438 if not NO_VERIFY :
405439 logging .info ("Verifying snapshot prior to update..." )
406- pre_all_units , pre_failed_units = verify_snapshot ()
440+ pre_all_units , pre_failed_units = verify_units ()
441+ pre_failed_progs = verify_programs (TMP_MOUNT_DIR )
407442 if COMMAND == "dup" :
408443 # check if dup has anything to do
409444 logging .info ("Checking for packages to upgrade..." )
@@ -461,12 +496,17 @@ chroot {TMP_MOUNT_DIR} bash -c "export PS1='atomic-update:\${{PWD}} # '; exec ba
461496 # verify snapshot after update
462497 if not NO_VERIFY :
463498 logging .info ("Verifying snapshot post update..." )
464- post_all_units , post_failed_units = verify_snapshot ()
499+ post_all_units , post_failed_units = verify_units ()
465500 newly_failed_units = list ( set (post_failed_units ) - set (pre_failed_units ) )
466501 update_failed_units = [unit for unit in newly_failed_units if unit in pre_all_units ]
467- if update_failed_units :
468- logging .error (f"Discarding snapshot { atomic_snap } as the following " \
469- f"systemd units have failed since update: { ', ' .join (update_failed_units )} " )
502+ post_failed_progs = verify_programs (TMP_MOUNT_DIR )
503+ newly_failed_progs = list ( set (post_failed_progs ) - set (pre_failed_progs ) )
504+ if update_failed_units or newly_failed_progs :
505+ msg = f"Discarding snapshot { atomic_snap } as new errors were detected after the update. "
506+ msg += f"The following programs have failed to run: { ', ' .join (newly_failed_progs )} . " if newly_failed_progs else ""
507+ msg += f"The following systemd units have failed: { ', ' .join (update_failed_units )} . " if update_failed_units else ""
508+ msg = msg .rstrip ()
509+ logging .error (msg )
470510 shell_exec (f"snapper -c { snapper_root_config } delete { atomic_snap } " )
471511 cleanup ()
472512 sys .exit ()
0 commit comments