3131VERSION = "0.1.0"
3232ZYPPER_PID_FILE = "/run/zypp.pid"
3333VALID_CMD = ["dup" , "run" , "rollback" ]
34- VALID_OPT = ["--reboot" , "--apply" , "--shell" , "--continue" , "--debug " , "--help " , "--version " , "--no-confirm " ]
34+ VALID_OPT = ["--reboot" , "--apply" , "--shell" , "--continue" , "--no-verify " , "--interactive " , "--debug " , "--help" , "--version " ]
3535
3636# Command help/usage info
3737help_text = """
5050 --apply - Switch into default snapshot without reboot
5151 --shell - Open shell in new snapshot before exiting
5252 --continue [number] - Use latest or given snapshot as base
53- --no-verify - Skip verification of new snapshot
53+ --no-verify - Skip verification of snapshot
54+ --interactive - Run dup in interactive mode
5455 --debug - Enable debug output
5556 --help - Print this help and exit
5657 --version - Print version number and exit
57- --no-confirm - Automatic yes to prompts, run non-interactively
5858"""
5959
6060################################
@@ -181,7 +181,7 @@ def sigint_handler(signum, frame):
181181 sys .exit (1 )
182182
183183DEBUG = True if "--debug" in OPT else False
184- NO_CONFIRM = True if "--no-confirm " in OPT else False
184+ CONFIRM = True if "--interactive " in OPT else False
185185REBOOT = True if "--reboot" in OPT else False
186186APPLY = True if "--apply" in OPT else False
187187SHELL = True if "--shell" in OPT else False
@@ -195,6 +195,27 @@ def sigint_handler(signum, frame):
195195 level = logging .DEBUG if DEBUG else logging .INFO ,
196196)
197197
198+ # check if there's a snapshot number provided to continue from
199+ continue_num = None
200+ if "--continue" in OPT :
201+ try :
202+ continue_num = int (sys .argv [sys .argv .index ("--continue" ) + 1 ])
203+ if not continue_num in range (1 , 999999 ):
204+ logging .error ("Invalid value for option '--continue'. Must be between 1 to 999999 (inclusive)" )
205+ sys .exit (1 )
206+ except ValueError :
207+ logging .debug ("No numerical value provided for option '--continue'" )
208+ pass
209+ except IndexError :
210+ logging .debug ("No value provided for option '--continue'" )
211+ pass
212+
213+ if continue_num :
214+ ret = os .system (f"btrfs subvolume list / | grep '@/.snapshots/{ continue_num } /snapshot'" )
215+ if ret != 0 :
216+ logging .error (f"Provided snapshot { continue_num } for option '--continue' does not exist" )
217+ sys .exit (1 )
218+
198219# Bail out if we're not root
199220if os .getuid () != 0 :
200221 logging .error ("Bailing out, program must be run with root privileges" )
@@ -245,6 +266,10 @@ def sigint_handler(signum, frame):
245266 # get active and default snapshot number
246267 active_snap , default_snap = get_snaps (snapper_root_config )
247268 logging .debug (f"Active snapshot number: { active_snap } , Default snapshot number: { default_snap } " )
269+ if CONTINUE :
270+ active_snap = default_snap
271+ if continue_num :
272+ active_snap = continue_num
248273 # create new read-write snapshot to perform dup in
249274 out , ret = shell_exec (f"snapper -c { snapper_root_config } create -c number " \
250275 f"-d 'Atomic update of #{ active_snap } ' " \
@@ -255,6 +280,7 @@ def sigint_handler(signum, frame):
255280 # get latest atomic snapshot
256281 atomic_snap = get_atomic_snap (snapper_root_config )
257282 logging .debug (f"Latest atomic snapshot number: { atomic_snap } " )
283+ logging .info (f"Using snapshot { active_snap } as base for new snapshot { atomic_snap } " )
258284 snap_subvol = f"@/.snapshots/{ atomic_snap } /snapshot"
259285 snap_dir = snap_subvol .lstrip ("@" )
260286 # check the latest atomic snapshot exists
@@ -289,18 +315,50 @@ def sigint_handler(signum, frame):
289315 cleanup ()
290316 sys .exit ()
291317 logging .info ("Performing atomic distribution upgrade..." )
292- ret = os .system (f"zypper --root { TMP_DIR } { '--non-interactive ' if NO_CONFIRM else '' } --no-cd dist-upgrade" )
318+ ret = os .system (f"zypper --root { TMP_DIR } { '' if CONFIRM else '--non-interactive ' } --no-cd dist-upgrade" )
293319 if ret != 0 :
294320 logging .error (f"Zypper returned exit code { ret } . Discarding snapshot { atomic_snap } " )
295321 shell_exec (f"snapper -c { snapper_root_config } delete { atomic_snap } " )
296322 cleanup ()
297323 sys .exit (9 )
298324 logging .info (f"Distribution upgrade completed successfully" )
325+ if SHELL :
326+ logging .info (f"Opening chroot in snapshot { atomic_snap } " )
327+ logging .info ("Continue with 'exit' or discard with 'exit 1'" )
328+ ret = os .system (f"chroot { snap_dir } env PS1='atomic-update:${{PWD}} # ' bash --noprofile --norc" )
329+ if ret != 0 :
330+ logging .error (f"Shell returned exit code { ret } . Discarding snapshot { atomic_snap } " )
331+ shell_exec (f"snapper -c { snapper_root_config } delete { atomic_snap } " )
332+ cleanup ()
333+ sys .exit ()
299334 logging .info (f"Setting snapshot { atomic_snap } ({ snap_dir } ) as the new default" )
300335 shell_exec (f"snapper -c { snapper_root_config } modify --default { atomic_snap } " )
301336 # perform cleanup
302337 cleanup ()
303338 if REBOOT :
339+ logging .info ("Rebooting now..." )
304340 os .system ("systemctl reboot" )
305341 if APPLY :
306- pass
342+ logging .info (f"Using default snapshot { atomic_snap } to replace running system..." )
343+ logging .info ("Applying /usr..." )
344+ os .system (f"mount -o bind { snap_dir } /usr /usr" )
345+ # find subvols under /usr and mount them
346+ out , ret = shell_exec ("LC_ALL=C btrfs subvolume list / | grep -v snapshots | grep '@/usr' | awk '{print $9}'" )
347+ for subvol in out .split ("\n " ):
348+ subvol = subvol .lstrip ("@" )
349+ os .system (f"mount -o bind { snap_dir } { subvol } { subvol } " )
350+ logging .info ("Applying /etc..." )
351+ os .system (f"mount -o bind { snap_dir } /etc /etc" )
352+ logging .info ("Applying /boot..." )
353+ os .system (f"mount -o bind { snap_dir } /boot /boot" )
354+ # find subvols under /boot and mount them
355+ out , ret = shell_exec ("LC_ALL=C btrfs subvolume list / | grep -v snapshots | grep '@/boot' | awk '{print $9}'" )
356+ for subvol in out .split ("\n " ):
357+ subvol = subvol .lstrip ("@" )
358+ os .system (f"mount -o bind { snap_dir } { subvol } { subvol } " )
359+ logging .info ("Executing systemctl daemon-reexec..." )
360+ os .system ("systemctl daemon-reexec" )
361+ logging .info ("Executing systemd-tmpfiles --create..." )
362+ os .system ("systemd-tmpfiles --create" )
363+ logging .info ("Applied default snapshot as new base for running system!" )
364+ logging .info ("Running processes will not be restarted automatically." )
0 commit comments