1212
1313CUSTOM_EXTRAS = {"steam" }
1414
15+ FALLBACK_MIRRORS = [
16+ "https://mirrors.kernel.org/archlinux/$repo/os/$arch" ,
17+ "https://mirror.rackspace.com/archlinux/$repo/os/$arch" ,
18+ "https://archlinux.uk.mirror.allworldit.com/archlinux/$repo/os/$arch" ,
19+ "https://mirror.one.com/archlinux/$repo/os/$arch" ,
20+ "https://archlinux.thaller.ws/archlinux/$repo/os/$arch" ,
21+ "https://ftp.linux.org.tr/archlinux/$repo/os/$arch" ,
22+ ]
23+
24+
25+ def detect_microcode ():
26+ try :
27+ with open ("/proc/cpuinfo" ) as f :
28+ cpuinfo = f .read ().lower ()
29+ if "authenticamd" in cpuinfo :
30+ return "amd-ucode"
31+ elif "genuineintel" in cpuinfo :
32+ return "intel-ucode"
33+ except Exception :
34+ pass
35+ return None
36+
37+
38+ def check_network (lfn , retries = 3 , delay = 5 ):
39+ import urllib .request
40+ for attempt in range (retries ):
41+ try :
42+ urllib .request .urlopen ("https://archlinux.org" , timeout = 10 )
43+ lfn ("network connectivity verified" )
44+ return True
45+ except Exception :
46+ if attempt < retries - 1 :
47+ lfn (f"[yellow]no network, retrying in { delay } s... ({ attempt + 1 } /{ retries } )[/]" )
48+ time .sleep (delay )
49+ return False
50+
51+
52+ def rank_mirrors (lfn ):
53+ if not shutil .which ("reflector" ):
54+ lfn ("[yellow]reflector not found, skipping mirror ranking[/]" )
55+ return False
56+ lfn ("ranking mirrors by speed..." )
57+ r = subprocess .run (
58+ ["reflector" , "--latest" , "20" , "--protocol" , "https" , "--sort" , "rate" ,
59+ "--save" , "/etc/pacman.d/mirrorlist" ],
60+ capture_output = True , text = True , timeout = 60
61+ )
62+ if r .returncode == 0 :
63+ lfn ("mirrors ranked successfully" )
64+ return True
65+ lfn (f"[yellow]reflector failed: { r .stderr .strip ()[:200 ]} [/]" )
66+ return False
67+
68+
69+ def set_fallback_mirrors (lfn ):
70+ lfn ("setting fallback mirrors..." )
71+ mirrorlist = "/etc/pacman.d/mirrorlist"
72+ try :
73+ with open (mirrorlist , "w" ) as f :
74+ f .write ("## mochios fallback mirrors\n " )
75+ for m in FALLBACK_MIRRORS :
76+ f .write (f"Server = { m } \n " )
77+ lfn ("fallback mirrors configured" )
78+ except Exception as e :
79+ lfn (f"[yellow]could not write mirrorlist: { e } [/]" )
80+
81+
82+ def pacstrap_with_fallback (target , pkgs , abort_flag , lfn , timeout = 900 ):
83+ try :
84+ abortable_run (["pacstrap" , "-K" , target ] + pkgs , abort_flag , timeout = timeout , log_fn = lfn )
85+ return True
86+ except (subprocess .CalledProcessError , subprocess .TimeoutExpired ) as e :
87+ err = str (e )[:300 ]
88+ lfn (f"[yellow]pacstrap failed, trying fallback mirrors... ({ err } )[/]" )
89+ set_fallback_mirrors (lfn )
90+ try :
91+ abortable_run (["pacstrap" , "-K" , target ] + pkgs , abort_flag , timeout = timeout , log_fn = lfn )
92+ return True
93+ except Exception as e2 :
94+ lfn (f"[red]pacstrap failed even with fallback mirrors: { e2 } [/]" )
95+ raise
96+
1597_CLEANUP_TARGET = [None ]
1698
1799def _unmount_all ():
18100 target = _CLEANUP_TARGET [0 ]
19101 if not target or not os .path .ismount (target ):
20102 return
21- for mp in [f"{ target } /var/cache " , f"{ target } /var" , f"{ target } /home " , f"{ target } /boot " , target ]:
103+ for mp in [f"{ target } /boot " , f"{ target } /var/cache " , f"{ target } /var " , f"{ target } /home " , target ]:
22104 subprocess .run (["umount" , "-l" , mp ], capture_output = True , check = False , timeout = 10 )
23105 subprocess .run (["umount" , "-l" , "/mnt/btrfs_tmp" ], capture_output = True , check = False , timeout = 10 )
24106 subprocess .run (["swapoff" , "-a" ], capture_output = True , check = False , timeout = 10 )
@@ -141,6 +223,45 @@ def retry_cmd(cmd, lfn, max_retries=5, delay=2, **kwargs):
141223 raise
142224
143225
226+ def detect_partitions (disk ):
227+ """detect existing partitions on a disk. returns list of dicts with name, size, type, mountpoint, fstype."""
228+ partitions = []
229+ try :
230+ r = subprocess .run (
231+ ["lsblk" , "-Jno" , "NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL" , disk ],
232+ capture_output = True , text = True , timeout = 5
233+ )
234+ import json
235+ data = json .loads (r .stdout )
236+ for dev in data .get ("blockdevices" , []):
237+ for child in dev .get ("children" , []):
238+ partitions .append ({
239+ "name" : f"/dev/{ child ['name' ]} " ,
240+ "size" : child .get ("size" , "?" ),
241+ "type" : child .get ("type" , "?" ),
242+ "fstype" : child .get ("fstype" , "" ),
243+ "mountpoint" : child .get ("mountpoint" , "" ),
244+ "label" : child .get ("label" , "" ),
245+ })
246+ except Exception :
247+ pass
248+ return partitions
249+
250+
251+ def detect_disk_partitions_summary (disk ):
252+ """return a human-readable summary of partitions on a disk."""
253+ parts = detect_partitions (disk )
254+ if not parts :
255+ return "no partitions found (empty or unformatted disk)"
256+ lines = []
257+ for p in parts :
258+ label = f" [{ p ['label' ]} ]" if p ["label" ] else ""
259+ mount = f" mounted at { p ['mountpoint' ]} " if p ["mountpoint" ] else ""
260+ fstype = f" ({ p ['fstype' ]} )" if p ["fstype" ] else ""
261+ lines .append (f" { p ['name' ]} { p ['size' ]} { fstype } { label } { mount } " )
262+ return "\n " .join (lines )
263+
264+
144265def select_disk (disk ):
145266 if not disk or not disk .startswith ("/dev/" ):
146267 raise ValueError (f"invalid disk: { disk } " )
@@ -194,7 +315,7 @@ def setup_btrfs(root_part, target, lfn):
194315 tmp = "/mnt/btrfs_tmp"
195316 os .makedirs (tmp , exist_ok = True )
196317 run (["mount" , "-o" , "subvol=/" , root_part , tmp ])
197- for sv in ["root_a" , "root_b" , "home" , "snapshots" , " var" , "cache" ]:
318+ for sv in ["root_a" , "root_b" , "home" , "var" , "cache" ]:
198319 run (["btrfs" , "subvolume" , "delete" , f"{ tmp } /{ sv } " ], check = False )
199320 run (["btrfs" , "subvolume" , "create" , f"{ tmp } /{ sv } " ])
200321 run (["umount" , tmp ])
@@ -241,13 +362,17 @@ def pacstrap_base(target, lfn, de="kde", greeter="sddm", bootloader="limine", ke
241362 "linux-firmware" ,
242363 "sudo" , "vim" , "nano" ,
243364 "gptfdisk" ,
244- "networkmanager" , "dhcpcd" , "reflector" ,
365+ "networkmanager" , "dhcpcd" ,
245366 "sbctl" ,
246367 "zsh" , "git" , "flatpak" ,
247368 ]
369+ ucode = detect_microcode ()
370+ if ucode :
371+ base .append (ucode )
372+ lfn (f"detected CPU microcode: { ucode } " )
248373 if filesystem == "btrfs" :
249374 base .extend (["btrfs-progs" , "snapper" ])
250- abortable_run ([ "pacstrap" , "-K" , target ] + base , abort_flag , timeout = 900 , log_fn = lfn )
375+ pacstrap_with_fallback ( target , base , abort_flag , lfn , timeout = 900 )
251376
252377 # step 2: desktop environment
253378 de_pkgs = {
@@ -264,7 +389,7 @@ def pacstrap_base(target, lfn, de="kde", greeter="sddm", bootloader="limine", ke
264389 }
265390 de_choice = de_pkgs .get (de , de_pkgs ["kde" ])
266391 lfn (f"installing { de } desktop..." )
267- abortable_run ([ "pacstrap" , "-K" , target ] + de_choice , abort_flag , timeout = 600 , log_fn = lfn )
392+ pacstrap_with_fallback ( target , de_choice , abort_flag , lfn , timeout = 600 )
268393
269394 # step 3: greeter + bootloader + extras
270395 greeter_pkgs = {
@@ -282,7 +407,7 @@ def pacstrap_base(target, lfn, de="kde", greeter="sddm", bootloader="limine", ke
282407 misc = greeter_pkgs .get (greeter , greeter_pkgs ["sddm" ]) + bl_pkgs .get (bootloader , []) + (extra_pkgs or [])
283408 if misc :
284409 lfn ("installing greeter, bootloader, and extras..." )
285- abortable_run ([ "pacstrap" , "-K" , target ] + misc , abort_flag , timeout = 300 , log_fn = lfn )
410+ pacstrap_with_fallback ( target , misc , abort_flag , lfn , timeout = 300 )
286411
287412
288413def configure_system (target , config , lfn , efi_uuid , swap_uuid , root_uuid ):
@@ -567,7 +692,7 @@ def install_mochiboot(target, disk, root_uuid, lfn, ch, kernels=None, filesystem
567692
568693def cleanup_mounts (target ):
569694 import subprocess as _sp
570- for mp in [f"{ target } /var/cache " , f"{ target } /var" , f"{ target } /home " , f"{ target } /boot " , target ]:
695+ for mp in [f"{ target } /boot " , f"{ target } /var/cache " , f"{ target } /var " , f"{ target } /home " , target ]:
571696 _sp .run (["umount" , "-l" , mp ], capture_output = True , check = False , timeout = 10 )
572697 _sp .run (["umount" , "-l" , "/mnt/btrfs_tmp" ], capture_output = True , check = False , timeout = 10 )
573698
@@ -652,13 +777,21 @@ def do_install(target="/mnt/mochios", config=None, log_fn=None, abort_flag=None)
652777 # ensure [mochi] repo is available for pacstrap
653778 mochi_conf = "\n [mochi]\n SigLevel = Optional TrustAll\n Server = https://github.com/iluxz/MochiOS/raw/gh-pages/os/x86_64\n "
654779 already_has_mochi = False
655- if os .path .exists ("/etc/pacman.conf" ):
656- with open ("/etc/pacman.conf" ) as check :
780+ target_pacman = f"{ target } /etc/pacman.conf"
781+ if os .path .exists (target_pacman ):
782+ with open (target_pacman ) as check :
657783 already_has_mochi = "[mochi]" in check .read ()
658784 if not already_has_mochi :
659- with open ("/etc/pacman.conf" , "a" ) as f :
785+ with open (target_pacman , "a" ) as f :
660786 f .write (mochi_conf )
661787
788+ # rank mirrors and check network before pacstrap
789+ if not check_network (log_fn ):
790+ log_fn ("[red]no network connectivity — pacstrap will likely fail[/]" )
791+ log_fn ("[yellow]connect to wifi or ethernet and try again[/]" )
792+ raise RuntimeError ("no network connectivity" )
793+ rank_mirrors (log_fn )
794+
662795 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 )
663796
664797 log_fn ("relaxing SigLevel for custom packages..." )
0 commit comments