Skip to content

Commit cbd4ac2

Browse files
committed
feat: add partition detection step to mochinstall TUI
1 parent 3fbb359 commit cbd4ac2

2 files changed

Lines changed: 70 additions & 16 deletions

File tree

‎installer.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,45 @@ def retry_cmd(cmd, lfn, max_retries=5, delay=2, **kwargs):
223223
raise
224224

225225

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+
226265
def select_disk(disk):
227266
if not disk or not disk.startswith("/dev/"):
228267
raise ValueError(f"invalid disk: {disk}")

‎mochiinstall_app.py‎

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
1515
from mochi_ascii import MOCHI_ASCII
16-
from installer import do_install, set_progress_cb
16+
from installer import do_install, set_progress_cb, detect_partitions, detect_disk_partitions_summary
1717

1818

19-
STEP_NAMES = ["Hostname", "Disk", "Filesystem", "Kernel", "Extras", "Desktop & Boot", "User", "Review"]
19+
STEP_NAMES = ["Hostname", "Disk", "Partitions", "Filesystem", "Kernel", "Extras", "Desktop & Boot", "User", "Review"]
2020

2121

2222
class LogUpdate(Message):
@@ -111,12 +111,12 @@ def on_mount(self) -> None:
111111
self.render_step()
112112

113113
def render_step(self) -> None:
114-
if not (0 <= self.step < 8):
114+
if not (0 <= self.step < 9):
115115
self.app.pop_screen()
116116
return
117117
self.step_bar.update(self.step_bar.render_step(self.step))
118118
self.body.remove_children()
119-
step_fn = [self.step_hostname, self.step_disk, self.step_fs, self.step_kernel, self.step_extra, self.step_de_boot, self.step_user, self.step_summary][self.step]
119+
step_fn = [self.step_hostname, self.step_disk, self.step_partition, self.step_fs, self.step_kernel, self.step_extra, self.step_de_boot, self.step_user, self.step_summary][self.step]
120120
step_fn()
121121
self._focus_step()
122122

@@ -129,15 +129,17 @@ def _focus_step(self) -> None:
129129
elif self.step == 1:
130130
self._try_focus_sel("#disk_list")
131131
elif self.step == 2:
132-
self._try_focus_sel("#fs")
132+
self._try_focus_sel("#disk_mode")
133133
elif self.step == 3:
134-
self._try_focus_sel("#kernel")
134+
self._try_focus_sel("#fs")
135135
elif self.step == 4:
136-
self._try_focus_sel("#extra_pkgs")
136+
self._try_focus_sel("#kernel")
137137
elif self.step == 5:
138+
self._try_focus_sel("#extra_pkgs")
139+
elif self.step == 6:
138140
self._try_focus_sel("#de")
139141
self._try_focus_sel("#bootloader")
140-
elif self.step == 6:
142+
elif self.step == 7:
141143
self._try_focus("#username", Input)
142144

143145
def _try_focus(self, selector, widget_type):
@@ -159,7 +161,7 @@ def _push_next(self) -> bool:
159161
hint = self.query_one("#disk_hint", Label) if self.body.query("#disk_hint") else None
160162
if hint: hint.update("[red]select a disk first![/]")
161163
return False
162-
if self.step == 6:
164+
if self.step == 7:
163165
pw = self.config.get("password", "")
164166
pw_c = self.config.get("password_confirm", "")
165167
hint = self.query_one("#pw_hint", Label) if self.body.query("#pw_hint") else None
@@ -170,7 +172,7 @@ def _push_next(self) -> bool:
170172
if hint: hint.update("[red]passwords do not match[/]")
171173
return False
172174
self.collect_current()
173-
if self.step == 7:
175+
if self.step == 8:
174176
if not self.config.get("disk"):
175177
self.step = 1
176178
self.render_step()
@@ -293,6 +295,19 @@ def step_disk(self) -> None:
293295
self._nav(can_next=has_disk, next_label="next")
294296
self.body.mount(self._wrap(*kids))
295297

298+
def step_partition(self) -> None:
299+
"""show partition info for selected disk."""
300+
disk = self.config.get("disk", "")
301+
kids = [Static(f"[bold]partitions on {disk}[/]")]
302+
303+
part_summary = detect_disk_partitions_summary(disk)
304+
kids.append(Static(f"[dim]{part_summary}[/]"))
305+
kids.append(Static(""))
306+
kids.append(Static("[dim]full 'use existing partition' mode coming soon — currently wipes the whole disk[/]"))
307+
308+
self._nav()
309+
self.body.mount(self._wrap(*kids))
310+
296311
def step_fs(self) -> None:
297312
fs = self.config.get("filesystem", "btrfs")
298313
self.body.mount(self._wrap(
@@ -383,7 +398,7 @@ def step_summary(self) -> None:
383398
Static("[bold underline]review configuration[/]"),
384399
Static(""),
385400
Static(f" hostname: [white]{c['hostname']}[/]"),
386-
Static(f" disk: [red]{c['disk'] or 'NOT SELECTED'}[/] {'[red bold](required!)' if not disk_set else '(will be wiped)'}"),
401+
Static(f" disk: [red]{c['disk'] or 'NOT SELECTED'}[/] {'[red bold](required!)' if not disk_set else '[dim](will be wiped)[/]'}"),
387402
Static(f" filesystem: [white]{c.get('filesystem', 'btrfs')}[/]"),
388403
Static(f" desktop: [white]{c['de']}[/]"),
389404
Static(f" greeter: [white]{c.get('greeter', 'sddm')}[/]"),
@@ -405,17 +420,17 @@ def collect_current(self) -> None:
405420
sel = self.query_one("#disk_list", SelectionList)
406421
if sel.selected:
407422
self.config["disk"] = sel.selected[0]
408-
elif self.step == 2:
423+
elif self.step == 3:
409424
sel = self.query_one("#fs", SelectionList)
410425
if sel.selected:
411426
self.config["filesystem"] = sel.selected[0]
412-
elif self.step == 3:
427+
elif self.step == 4:
413428
sel = self.query_one("#kernel", SelectionList)
414429
self.config["kernels"] = sel.selected if sel.selected else ["linux"]
415-
elif self.step == 4:
430+
elif self.step == 5:
416431
sel = self.query_one("#extra_pkgs", SelectionList)
417432
self.config["extra_pkgs"] = sel.selected if sel.selected else []
418-
elif self.step == 5:
433+
elif self.step == 6:
419434
sel_de = self.query_one("#de", SelectionList)
420435
sel_bl = self.query_one("#bootloader", SelectionList)
421436
sel_gr = self.query_one("#greeter", SelectionList)
@@ -425,7 +440,7 @@ def collect_current(self) -> None:
425440
self.config["bootloader"] = sel_bl.selected[0]
426441
if sel_gr.selected:
427442
self.config["greeter"] = sel_gr.selected[0]
428-
elif self.step == 6:
443+
elif self.step == 7:
429444
u = self.query_one("#username", Input)
430445
p = self.query_one("#password", Input)
431446
pc = self.query_one("#password_confirm", Input)

0 commit comments

Comments
 (0)