@@ -211,7 +211,20 @@ def setup_btrfs(root_part, target, lfn):
211211 run (["mount" , "-o" , "compress=zstd,subvol=cache" , root_part , f"{ target } /var/cache" ])
212212
213213
214- def pacstrap_base (target , lfn , de = "kde" , greeter = "sddm" , bootloader = "limine" , kernels = None , extra_pkgs = None , abort_flag = None ):
214+ def format_ext4 (part , lfn ):
215+ lfn (f"formatting root { part } as ext4..." )
216+ retry_cmd (["mkfs.ext4" , "-F" , "-L" , "MOCHIOS" , part ], lfn , timeout = 120 )
217+
218+
219+ def setup_ext4 (root_part , target , lfn ):
220+ lfn ("mounting ext4 root..." )
221+ os .makedirs (target , exist_ok = True )
222+ run (["mount" , root_part , target ])
223+ os .makedirs (f"{ target } /boot" , exist_ok = True )
224+ os .makedirs (f"{ target } /home" , exist_ok = True )
225+
226+
227+ def pacstrap_base (target , lfn , de = "kde" , greeter = "sddm" , bootloader = "limine" , kernels = None , extra_pkgs = None , abort_flag = None , filesystem = "btrfs" ):
215228 if kernels is None :
216229 kernels = ["linux" ]
217230
@@ -226,12 +239,14 @@ def pacstrap_base(target, lfn, de="kde", greeter="sddm", bootloader="limine", ke
226239 "base" ,
227240 * kernel_pkgs ,
228241 "linux-firmware" ,
229- "btrfs-progs" , "snapper" , " sudo" , "vim" , "nano" ,
242+ "sudo" , "vim" , "nano" ,
230243 "gptfdisk" ,
231244 "networkmanager" , "dhcpcd" , "reflector" ,
232245 "sbctl" ,
233246 "zsh" , "git" , "flatpak" ,
234247 ]
248+ if filesystem == "btrfs" :
249+ base .extend (["btrfs-progs" , "snapper" ])
235250 abortable_run (["pacstrap" , "-K" , target ] + base , abort_flag , timeout = 900 , log_fn = lfn )
236251
237252 # step 2: desktop environment
@@ -240,7 +255,8 @@ def pacstrap_base(target, lfn, de="kde", greeter="sddm", bootloader="limine", ke
240255 "kwin" , "konsole" , "dolphin" , "kate" , "gwenview" ,
241256 "kwallet-pam" , "breeze" , "breeze-gtk" ,
242257 "pipewire" , "pipewire-pulse" , "wireplumber" , "kpipewire" ,
243- "plasma-nm" ],
258+ "plasma-nm" , "systemsettings" , "powerdevil" , "plasma-pa" ,
259+ "polkit-kde-agent" , "xdg-desktop-portal-kde" , "bluedevil" ],
244260 "gnome" : ["gnome" , "gnome-extra" ],
245261 "hyprland" : ["hyprland" , "kitty" , "xdg-desktop-portal-hyprland" ,
246262 "wofi" , "waybar" , "dunst" , "polkit-kde-agent" ,
@@ -278,6 +294,7 @@ def configure_system(target, config, lfn, efi_uuid, swap_uuid, root_uuid):
278294 bootloader = config .get ("bootloader" , "limine" )
279295 kernels = config .get ("kernels" , ["linux" ])
280296 disk = config .get ("disk" , "" )
297+ filesystem = config .get ("filesystem" , "btrfs" )
281298
282299 lfn ("configuring system..." )
283300 t = Path (target )
@@ -299,25 +316,34 @@ def ch(cmd, to=120, check=True, input_data=None):
299316 ch (["mkinitcpio" , "-P" ], to = 300 )
300317
301318 lfn ("writing fstab..." )
302- fstab = (
303- f"UUID={ efi_uuid } /boot vfat rw,noatime,fmask=0022,dmask=0022 0 2\n "
304- f"UUID={ swap_uuid } swap swap defaults 0 0\n "
305- f"UUID={ root_uuid } /mnt/btrfs btrfs rw,noatime,compress=zstd,subvolid=5 0 0\n "
306- f"UUID={ root_uuid } / btrfs rw,noatime,compress=zstd,subvol=root_a 0 0\n "
307- f"UUID={ root_uuid } /home btrfs rw,noatime,compress=zstd,subvol=home 0 0\n "
308- f"UUID={ root_uuid } /var btrfs rw,noatime,compress=zstd,subvol=var 0 0\n "
309- f"UUID={ root_uuid } /var/cache btrfs rw,noatime,compress=zstd,subvol=cache 0 0\n "
310- )
319+ if filesystem == "btrfs" :
320+ fstab = (
321+ f"UUID={ efi_uuid } /boot vfat rw,noatime,fmask=0022,dmask=0022 0 2\n "
322+ f"UUID={ swap_uuid } swap swap defaults 0 0\n "
323+ f"UUID={ root_uuid } /mnt/btrfs btrfs rw,noatime,compress=zstd,subvolid=5 0 0\n "
324+ f"UUID={ root_uuid } / btrfs rw,noatime,compress=zstd,subvol=root_a 0 0\n "
325+ f"UUID={ root_uuid } /home btrfs rw,noatime,compress=zstd,subvol=home 0 0\n "
326+ f"UUID={ root_uuid } /var btrfs rw,noatime,compress=zstd,subvol=var 0 0\n "
327+ f"UUID={ root_uuid } /var/cache btrfs rw,noatime,compress=zstd,subvol=cache 0 0\n "
328+ )
329+ os .makedirs (f"{ target } /mnt/btrfs" , exist_ok = True )
330+ else :
331+ fstab = (
332+ f"UUID={ efi_uuid } /boot vfat rw,noatime,fmask=0022,dmask=0022 0 2\n "
333+ f"UUID={ swap_uuid } swap swap defaults 0 0\n "
334+ f"UUID={ root_uuid } / ext4 rw,noatime 0 1\n "
335+ f"UUID={ root_uuid } /home ext4 rw,noatime 0 2\n "
336+ )
311337 (t / "etc/fstab" ).write_text (fstab )
312- os .makedirs (f"{ target } /mnt/btrfs" , exist_ok = True )
313338 ch (["systemctl" , "daemon-reload" ])
314339
315- (t / "etc/abroot.conf" ).write_text (
316- f"ROOT_PART=\" UUID={ root_uuid } \" \n "
317- f"BTRFS_MOUNT=\" /mnt/btrfs\" \n "
318- f"ACTIVE_ROOT=\" root_a\" \n "
319- f"NEXT_ROOT=\" root_b\" \n "
320- )
340+ if filesystem == "btrfs" :
341+ (t / "etc/abroot.conf" ).write_text (
342+ f"ROOT_PART=\" UUID={ root_uuid } \" \n "
343+ f"BTRFS_MOUNT=\" /mnt/btrfs\" \n "
344+ f"ACTIVE_ROOT=\" root_a\" \n "
345+ f"NEXT_ROOT=\" root_b\" \n "
346+ )
321347
322348 lfn ("enabling multilib repo..." )
323349 pmconf = t / "etc/pacman.conf"
@@ -353,11 +379,11 @@ def ch(cmd, to=120, check=True, input_data=None):
353379 lfn (" [yellow]mochios key not found, skipping[/]" )
354380
355381 if bootloader == "limine" :
356- install_limine (target , disk , root_uuid , lfn , ch , kernels = kernels )
382+ install_limine (target , disk , root_uuid , lfn , ch , kernels = kernels , filesystem = filesystem )
357383 elif bootloader == "grub" :
358- install_grub (target , disk , root_uuid , lfn , ch , kernels = kernels )
384+ install_grub (target , disk , root_uuid , lfn , ch , kernels = kernels , filesystem = filesystem )
359385 elif bootloader == "mochiboot" :
360- install_mochiboot (target , disk , root_uuid , lfn , ch , kernels = kernels )
386+ install_mochiboot (target , disk , root_uuid , lfn , ch , kernels = kernels , filesystem = filesystem )
361387
362388 lfn ("signing boot files with sbctl..." )
363389 keydir = t / "var/lib/sbctl/keys/db"
@@ -401,19 +427,20 @@ def get_uuid(part):
401427 return run (["blkid" , "-s" , "UUID" , "-o" , "value" , part ], capture = True , check = True ).stdout .strip ()
402428
403429
404- def install_limine (target , disk , root_uuid , lfn , ch , kernels = None ):
430+ def install_limine (target , disk , root_uuid , lfn , ch , kernels = None , filesystem = "btrfs" ):
405431 if kernels is None :
406432 kernels = ["linux" ]
407433 lfn ("installing limine..." )
408434
435+ root_flags = "rootflags=subvol=root_a " if filesystem == "btrfs" else ""
409436 config_lines = ["TIMEOUT=5" , "VERBOSE=no" , "" ]
410437 for k in kernels :
411438 vmlinuz = f"/boot/vmlinuz-{ k } "
412439 label = f"MOCHIOS ({ k } )" if len (kernels ) > 1 else "MOCHIOS"
413440 config_lines .append (f":{ label } " )
414441 config_lines .append (" PROTOCOL=linux" )
415442 config_lines .append (f" KERNEL_PATH={ vmlinuz } " )
416- config_lines .append (f" CMDLINE=root=UUID={ root_uuid } rootflags=subvol=root_a rw loglevel=4" )
443+ config_lines .append (f" CMDLINE=root=UUID={ root_uuid } { root_flags } rw loglevel=4" )
417444 config_lines .append ("" )
418445
419446 config = "\n " .join (config_lines )
@@ -434,33 +461,36 @@ def install_limine(target, disk, root_uuid, lfn, ch, kernels=None):
434461 ch (["cp" , "/usr/share/limine/BOOTIA32.EFI" , "/boot/EFI/BOOT/BOOTIA32.EFI" ], to = 30 )
435462
436463
437- def install_grub (target , disk , root_uuid , lfn , ch , kernels = None ):
464+ def install_grub (target , disk , root_uuid , lfn , ch , kernels = None , filesystem = "btrfs" ):
438465 if kernels is None :
439466 kernels = ["linux" ]
440467 lfn ("installing grub..." )
441468
442469 grub_def = Path (target ) / "etc/default/grub"
443470 grub_text = grub_def .read_text () if grub_def .exists () else ""
471+ root_flags = "rootflags=subvol=root_a" if filesystem == "btrfs" else ""
444472 new_grub_text = []
445473 for line in grub_text .split ("\n " ):
446474 if line .startswith ("GRUB_CMDLINE_LINUX=" ):
447- new_grub_text .append ('GRUB_CMDLINE_LINUX="rootflags=subvol=root_a "' )
475+ new_grub_text .append (f 'GRUB_CMDLINE_LINUX="{ root_flags } "' )
448476 else :
449477 new_grub_text .append (line )
450478 if "GRUB_CMDLINE_LINUX=" not in grub_text :
451- new_grub_text .append ('GRUB_CMDLINE_LINUX="rootflags=subvol=root_a "' )
479+ new_grub_text .append (f 'GRUB_CMDLINE_LINUX="{ root_flags } "' )
452480 grub_def .write_text ("\n " .join (new_grub_text ))
453481
454482 ch (["grub-install" , "--target=x86_64-efi" , "--efi-directory=/boot" , "--bootloader-id=MOCHIOS" , "--removable" ])
455483 ch (["grub-install" , "--target=i386-pc" , disk ])
456484 ch (["grub-mkconfig" , "-o" , "/boot/grub/grub.cfg" ])
457485
458486
459- def install_mochiboot (target , disk , root_uuid , lfn , ch , kernels = None ):
487+ def install_mochiboot (target , disk , root_uuid , lfn , ch , kernels = None , filesystem = "btrfs" ):
460488 if kernels is None :
461489 kernels = ["linux" ]
462490 lfn ("installing mochiboot..." )
463491
492+ root_flags = "rootflags=subvol=root_a " if filesystem == "btrfs" else ""
493+
464494 config_lines = [
465495 "TIMEOUT=5" ,
466496 "VERBOSE=no" ,
@@ -488,12 +518,12 @@ def install_mochiboot(target, disk, root_uuid, lfn, ch, kernels=None):
488518 config_lines .append (f":{ label } " )
489519 config_lines .append (" PROTOCOL=linux" )
490520 config_lines .append (f" KERNEL_PATH={ vmlinuz } " )
491- config_lines .append (f" CMDLINE=root=UUID={ root_uuid } rootflags=subvol=root_a rw loglevel=4" )
521+ config_lines .append (f" CMDLINE=root=UUID={ root_uuid } { root_flags } rw loglevel=4" )
492522 config_lines .append ("" )
493523 config_lines .append (":MOCHIOS (recovery mode)" )
494524 config_lines .append (" PROTOCOL=linux" )
495525 config_lines .append (f" KERNEL_PATH=/boot/vmlinuz-{ kernels [0 ]} " )
496- config_lines .append (f" CMDLINE=root=UUID={ root_uuid } rootflags=subvol=root_a rw loglevel=4 mochi.recovery" )
526+ config_lines .append (f" CMDLINE=root=UUID={ root_uuid } { root_flags } rw loglevel=4 mochi.recovery" )
497527 config_lines .append ("" )
498528
499529 config = "\n " .join (config_lines )
@@ -542,18 +572,43 @@ def cleanup_mounts(target):
542572 _sp .run (["umount" , "-l" , "/mnt/btrfs_tmp" ], capture_output = True , check = False , timeout = 10 )
543573
544574
575+ _INSTALL_LOCK = "/tmp/mochiinstall.lock"
576+
577+ def _acquire_lock ():
578+ try :
579+ fd = os .open (_INSTALL_LOCK , os .O_CREAT | os .O_EXCL | os .O_WRONLY , 0o644 )
580+ os .write (fd , str (os .getpid ()).encode ())
581+ os .close (fd )
582+ return True
583+ except FileExistsError :
584+ return False
585+
586+ def _release_lock ():
587+ try :
588+ os .remove (_INSTALL_LOCK )
589+ except OSError :
590+ pass
591+
592+
545593def do_install (target = "/mnt/mochios" , config = None , log_fn = None , abort_flag = None ):
546594 if log_fn is None :
547595 log_fn = print
548596 if config is None :
549597 config = {}
550598
599+ if not _acquire_lock ():
600+ raise RuntimeError ("another installer instance is already running" )
601+
551602 mounts_cleanup_needed = False
552603 _STEP_COUNT [0 ] = 0
553604 try :
554605 if abort_flag and abort_flag ():
555606 raise RuntimeError ("installation aborted" )
556607 log_fn ("starting mochios installation..." )
608+
609+ # pre-flight: unmount any leftover mounts from failed installs
610+ _unmount_all ()
611+
557612 disk = select_disk (config .get ("disk" ))
558613 bios_part , efi_part , swap_part , root_part_p = partition_disk (disk , log_fn )
559614 mounts_cleanup_needed = True
@@ -562,17 +617,31 @@ def do_install(target="/mnt/mochios", config=None, log_fn=None, abort_flag=None)
562617 raise RuntimeError ("installation aborted" )
563618 format_efi (efi_part , log_fn )
564619 format_swap (swap_part , log_fn )
565- format_btrfs (root_part_p , log_fn )
620+
621+ filesystem = config .get ("filesystem" , "btrfs" )
622+ if filesystem == "ext4" :
623+ format_ext4 (root_part_p , log_fn )
624+ else :
625+ format_btrfs (root_part_p , log_fn )
566626
567627 if abort_flag and abort_flag ():
568628 raise RuntimeError ("installation aborted" )
569629 _CLEANUP_TARGET [0 ] = target
570- setup_btrfs (root_part_p , target , log_fn )
630+ if filesystem == "ext4" :
631+ setup_ext4 (root_part_p , target , log_fn )
632+ else :
633+ setup_btrfs (root_part_p , target , log_fn )
571634
572635 efi_uuid = get_uuid (efi_part )
573636 swap_uuid = get_uuid (swap_part )
574637 root_uuid = get_uuid (root_part_p )
575638
639+ if filesystem == "btrfs" :
640+ log_fn ("configuring snapper for root..." )
641+ run (["arch-chroot" , target , "snapper" , "-c" , "root" , "create-config" , "/" ], check = False , timeout = 30 )
642+ run (["arch-chroot" , target , "systemctl" , "enable" , "snapper-timeline.timer" ], check = False )
643+ run (["arch-chroot" , target , "systemctl" , "enable" , "snapper-cleanup.timer" ], check = False )
644+
576645 run (["swapon" , swap_part ], check = False )
577646 run (["mount" , efi_part , f"{ target } /boot" ])
578647
@@ -582,11 +651,15 @@ def do_install(target="/mnt/mochios", config=None, log_fn=None, abort_flag=None)
582651
583652 # ensure [mochi] repo is available for pacstrap
584653 mochi_conf = "\n [mochi]\n SigLevel = Optional TrustAll\n Server = https://github.com/iluxz/MochiOS/raw/gh-pages/os/x86_64\n "
585- with open ("/etc/pacman.conf" , "a" ) as f :
586- if "[mochi]" not in open ("/etc/pacman.conf" ).read ():
654+ already_has_mochi = False
655+ if os .path .exists ("/etc/pacman.conf" ):
656+ with open ("/etc/pacman.conf" ) as check :
657+ already_has_mochi = "[mochi]" in check .read ()
658+ if not already_has_mochi :
659+ with open ("/etc/pacman.conf" , "a" ) as f :
587660 f .write (mochi_conf )
588661
589- pacstrap_base (target , log_fn , de = config .get ("de" , "kde" ), greeter = config .get ("greeter" , "sddm" ), bootloader = config .get ("bootloader" , "limine" ), kernels = config .get ("kernels" , ["linux" ]), extra_pkgs = repo_extra , abort_flag = abort_flag )
662+ pacstrap_base (target , log_fn , de = config .get ("de" , "kde" ), greeter = config .get ("greeter" , "sddm" ), bootloader = config .get ("bootloader" , "limine" ), kernels = config .get ("kernels" , ["linux" ]), extra_pkgs = repo_extra , abort_flag = abort_flag , filesystem = filesystem )
590663
591664 log_fn ("relaxing SigLevel for custom packages..." )
592665 pmconf_path = f"{ target } /etc/pacman.conf"
@@ -675,6 +748,7 @@ def do_install(target="/mnt/mochios", config=None, log_fn=None, abort_flag=None)
675748 log_fn (f"[dim]{ traceback .format_exc ()[- 500 :]} [/]" )
676749 return False
677750 finally :
751+ _release_lock ()
678752 if mounts_cleanup_needed :
679753 log_fn ("[yellow]cleaning up mounts after failure...[/]" )
680754 cleanup_mounts (target )
0 commit comments