Skip to content

Commit f9977da

Browse files
authored
Merge pull request #18 from frostyard/fixnbcinstall
feat: fix nbc install scripts
2 parents 51d2106 + 508340f commit f9977da

File tree

5 files changed

+218
-42
lines changed

5 files changed

+218
-42
lines changed

snow_first_setup/gtk/install-disk.ui

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@
77
<object class="AdwToolbarView">
88
<property name="content">
99
<object class="AdwStatusPage" id="status_page">
10-
<property name="icon-name">computer-symbolic</property>
1110
<property name="title" translatable="yes">Select Installation Disk</property>
12-
<property name="description" translatable="yes">Choose the physical disk where the system will be installed. All data on the selected disk will be erased.</property>
11+
<property name="description" translatable="yes">All data on the selected disk will be erased.</property>
1312
<child>
1413
<object class="AdwClamp">
1514
<property name="maximum-size">720</property>
1615
<child>
1716
<object class="GtkBox">
1817
<property name="orientation">vertical</property>
19-
<property name="spacing">24</property>
20-
<property name="margin-top">12</property>
18+
<property name="spacing">12</property>
2119
<child>
2220
<object class="AdwPreferencesGroup" id="disks_group">
2321
<property name="title" translatable="yes">Available Disks</property>
@@ -26,11 +24,10 @@
2624

2725
<child>
2826
<object class="AdwPreferencesGroup">
29-
<property name="title" translatable="yes">Filesystem</property>
27+
<property name="title" translatable="yes">Options</property>
3028
<child>
3129
<object class="AdwComboRow" id="fs_combo">
32-
<property name="title" translatable="yes">Filesystem Type</property>
33-
<property name="subtitle" translatable="yes">Select the filesystem for the installation</property>
30+
<property name="title" translatable="yes">Filesystem</property>
3431
<property name="model">
3532
<object class="GtkStringList">
3633
<items>
@@ -41,28 +38,36 @@
4138
</property>
4239
</object>
4340
</child>
44-
</object>
45-
</child>
46-
47-
<child>
48-
<object class="AdwPreferencesGroup" id="fde_group">
49-
<property name="title" translatable="yes">Encryption</property>
5041
<child>
51-
<object class="AdwActionRow" id="fde_row">
52-
<property name="title" translatable="yes">(Broken) Full Disk Encryption (LUKS)</property>
53-
<child type="suffix">
54-
<object class="GtkCheckButton" id="fde_checkbox">
55-
<property name="valign">center</property>
56-
</object>
57-
</child>
42+
<object class="AdwSwitchRow" id="fde_row">
43+
<property name="title" translatable="yes">Full Disk Encryption</property>
44+
<signal name="notify::active" handler="__on_fde_toggled" swapped="no"/>
45+
</object>
46+
</child>
47+
<child>
48+
<object class="AdwPasswordEntryRow" id="passphrase_entry">
49+
<property name="title" translatable="yes">Passphrase</property>
50+
<property name="visible">false</property>
51+
</object>
52+
</child>
53+
<child>
54+
<object class="AdwPasswordEntryRow" id="passphrase_confirm_entry">
55+
<property name="title" translatable="yes">Confirm Passphrase</property>
56+
<property name="visible">false</property>
57+
</object>
58+
</child>
59+
<child>
60+
<object class="AdwSwitchRow" id="tpm_row">
61+
<property name="title" translatable="yes">TPM Auto-unlock</property>
62+
<property name="visible">false</property>
5863
</object>
5964
</child>
6065
</object>
6166
</child>
6267

6368
<child>
6469
<object class="GtkLabel" id="no_disks_label">
65-
<property name="label" translatable="yes">No removable disks were found. If you expect a disk to appear, ensure it is connected and try again.</property>
70+
<property name="label" translatable="yes">No disks found. Ensure your disk is connected and try again.</property>
6671
<property name="visible">false</property>
6772
<property name="wrap">true</property>
6873
<property name="wrap-mode">word-char</property>

snow_first_setup/scripts/install-to-disk

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@ set -euo pipefail
33

44
# Install script using nbc (SNOW bootc installer)
55
# Outputs JSON Lines for streaming progress to the installer GUI
6+
#
7+
# Usage: install-to-disk <image> <filesystem> <device> [fde] [passphrase] [tpm2]
8+
# image - container image reference
9+
# filesystem - filesystem type (btrfs, ext4)
10+
# device - block device path
11+
# fde - "true" or "false" for full disk encryption (default: false)
12+
# passphrase - encryption passphrase (required if fde=true)
13+
# tpm2 - "true" or "false" for TPM2 auto-unlock (default: false)
614

7-
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then
8-
echo '{"type":"error","message":"Missing arguments. Usage: install-to-disk <image> <filesystem> <device> [fde]"}'
15+
if [ -z "${1:-}" ] || [ -z "${2:-}" ] || [ -z "${3:-}" ]; then
16+
echo '{"type":"error","message":"Missing arguments. Usage: install-to-disk <image> <filesystem> <device> [fde] [passphrase] [tpm2]"}'
917
exit 5
1018
fi
1119

@@ -23,13 +31,27 @@ IMAGE="$1"
2331
FILESYSTEM="$2"
2432
DEVICE="$3"
2533
FDE="${4:-false}"
34+
PASSPHRASE="${5:-}"
35+
TPM2="${6:-false}"
2636

2737
# Validate FDE parameter
2838
if [ "$FDE" != "true" ] && [ "$FDE" != "false" ]; then
2939
echo '{"type":"error","message":"FDE parameter must be '"'true'"' or '"'false'"', got: '"$FDE"'"}'
3040
exit 7
3141
fi
3242

43+
# Validate TPM2 parameter
44+
if [ "$TPM2" != "true" ] && [ "$TPM2" != "false" ]; then
45+
echo '{"type":"error","message":"TPM2 parameter must be '"'true'"' or '"'false'"', got: '"$TPM2"'"}'
46+
exit 8
47+
fi
48+
49+
# If FDE is enabled, require a passphrase
50+
if [ "$FDE" == "true" ] && [ -z "$PASSPHRASE" ]; then
51+
echo '{"type":"error","message":"FDE is enabled but no passphrase provided"}'
52+
exit 10
53+
fi
54+
3355
# Check that nbc is available
3456
if ! command -v nbc &> /dev/null; then
3557
echo '{"type":"error","message":"nbc command not found, cannot proceed with installation"}'
@@ -45,9 +67,15 @@ NBC_ARGS=(
4567
"--json"
4668
)
4769

48-
# Add FDE flag if enabled
70+
# Add FDE options if enabled
4971
if [ "$FDE" == "true" ]; then
50-
NBC_ARGS+=("--fde")
72+
NBC_ARGS+=("--encrypt")
73+
NBC_ARGS+=("--password" "$PASSPHRASE")
74+
75+
# Add TPM2 auto-unlock if requested
76+
if [ "$TPM2" == "true" ]; then
77+
NBC_ARGS+=("--tpm2")
78+
fi
5179
fi
5280

5381
# Run nbc install with JSON output streaming directly to stdout

snow_first_setup/views/install_confirm.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ def set_page_active(self):
6363
self.fs_label.set_text(_("<none>"))
6464
if getattr(self, 'fde_label', None) is not None:
6565
if fde_enabled:
66-
self.fde_label.set_text(_("Enabled"))
66+
tpm_enabled = getattr(self.__window, "install_tpm_enabled", False)
67+
if tpm_enabled:
68+
self.fde_label.set_text(_("Enabled (TPM auto-unlock)"))
69+
else:
70+
self.fde_label.set_text(_("Enabled"))
6771
else:
6872
self.fde_label.set_text(_("Disabled"))
6973

snow_first_setup/views/install_disk.py

Lines changed: 143 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,74 @@ class VanillaInstallDisk(Adw.Bin):
1919
disks_group = Gtk.Template.Child()
2020
no_disks_label = Gtk.Template.Child()
2121
fs_combo = Gtk.Template.Child()
22-
fde_checkbox = Gtk.Template.Child()
22+
fde_row = Gtk.Template.Child()
23+
passphrase_entry = Gtk.Template.Child()
24+
passphrase_confirm_entry = Gtk.Template.Child()
25+
tpm_row = Gtk.Template.Child()
2326

2427
def __init__(self, window, **kwargs):
2528
super().__init__(**kwargs)
2629
self.__window = window
2730
self.__selected_device = None
31+
# Persisted FDE settings
32+
self.__fde_enabled = False
33+
self.__fde_passphrase = ""
34+
self.__fde_passphrase_confirm = ""
35+
self.__tpm_enabled = False
2836
# store tuples of (action_row, radio_button)
2937
self.__rows = []
30-
# wire up FDE checkbox behavior (currently no extra inputs)
38+
# wire up FDE switch behavior
3139
try:
32-
self.fde_checkbox.connect("toggled", self.__on_fde_toggled)
40+
self.fde_row.connect("notify::active", self.__on_fde_toggled)
41+
except Exception:
42+
pass
43+
# wire up passphrase entry changes
44+
try:
45+
self.passphrase_entry.connect("changed", self.__on_passphrase_changed)
46+
self.passphrase_confirm_entry.connect("changed", self.__on_passphrase_changed)
47+
except Exception:
48+
pass
49+
# wire up TPM switch
50+
try:
51+
self.tpm_row.connect("notify::active", self.__on_tpm_toggled)
3352
except Exception:
3453
pass
3554

3655
def set_page_active(self):
3756
# Refresh available disks each time the page becomes active
3857
GLib.idle_add(self.refresh_drives)
58+
# Restore persisted FDE settings to UI
59+
self.__restore_fde_settings()
3960

4061
def set_page_inactive(self):
41-
return
62+
# Persist current FDE settings before leaving the page
63+
self.__save_fde_settings()
64+
65+
def __save_fde_settings(self):
66+
"""Save current FDE UI state to instance variables."""
67+
try:
68+
self.__fde_enabled = self.fde_row.get_active()
69+
self.__fde_passphrase = self.passphrase_entry.get_text()
70+
self.__fde_passphrase_confirm = self.passphrase_confirm_entry.get_text()
71+
self.__tpm_enabled = self.tpm_row.get_active()
72+
except Exception:
73+
pass
74+
75+
def __restore_fde_settings(self):
76+
"""Restore persisted FDE settings to UI widgets."""
77+
try:
78+
self.fde_row.set_active(self.__fde_enabled)
79+
self.passphrase_entry.set_text(self.__fde_passphrase)
80+
self.passphrase_confirm_entry.set_text(self.__fde_passphrase_confirm)
81+
self.tpm_row.set_active(self.__tpm_enabled)
82+
# Update visibility based on FDE state
83+
self.passphrase_entry.set_visible(self.__fde_enabled)
84+
self.passphrase_confirm_entry.set_visible(self.__fde_enabled)
85+
self.tpm_row.set_visible(self.__fde_enabled)
86+
if self.__fde_enabled:
87+
self.__update_passphrase_validation_ui()
88+
except Exception:
89+
pass
4290

4391
def finish(self):
4492
# Called when the user presses next. Ensure a device is selected and store it on the window.
@@ -61,11 +109,15 @@ def finish(self):
61109
fs = "btrfs"
62110
self.__window.install_target_fs = fs
63111

64-
# Handle Full Disk Encryption selection (no passphrase inputs)
65-
try:
66-
self.__window.install_fde_enabled = bool(self.fde_checkbox.get_active())
67-
except Exception:
68-
self.__window.install_fde_enabled = False
112+
# Handle Full Disk Encryption selection - save and use persisted values
113+
self.__save_fde_settings()
114+
self.__window.install_fde_enabled = self.__fde_enabled
115+
if self.__fde_enabled:
116+
self.__window.install_fde_passphrase = self.__fde_passphrase
117+
self.__window.install_tpm_enabled = self.__tpm_enabled
118+
else:
119+
self.__window.install_fde_passphrase = None
120+
self.__window.install_tpm_enabled = False
69121
return True
70122

71123
def refresh_drives(self):
@@ -154,10 +206,88 @@ def __on_radio_toggled(self, radio, path):
154206
else:
155207
row.remove_css_class("selected")
156208

209+
self.__update_ready_state()
210+
211+
def __validate_passphrase(self):
212+
"""Check if passphrase meets requirements: min 8 chars and both fields match."""
213+
passphrase = self.passphrase_entry.get_text()
214+
confirm = self.passphrase_confirm_entry.get_text()
215+
if len(passphrase) < 8:
216+
return False
217+
if passphrase != confirm:
218+
return False
219+
return True
220+
221+
def __update_passphrase_validation_ui(self):
222+
"""Update visual validation state for passphrase fields."""
223+
passphrase = self.passphrase_entry.get_text()
224+
confirm = self.passphrase_confirm_entry.get_text()
225+
226+
# Validate passphrase field: must be at least 8 characters
227+
if len(passphrase) == 0:
228+
self.passphrase_entry.add_css_class("error")
229+
elif len(passphrase) < 8:
230+
self.passphrase_entry.add_css_class("error")
231+
else:
232+
self.passphrase_entry.remove_css_class("error")
233+
234+
# Validate confirmation field: must match passphrase
235+
if len(confirm) == 0 or confirm != passphrase:
236+
self.passphrase_confirm_entry.add_css_class("error")
237+
else:
238+
self.passphrase_confirm_entry.remove_css_class("error")
239+
240+
def __clear_passphrase_validation_ui(self):
241+
"""Clear validation error styling from passphrase fields."""
242+
try:
243+
self.passphrase_entry.remove_css_class("error")
244+
self.passphrase_confirm_entry.remove_css_class("error")
245+
except Exception:
246+
pass
247+
248+
def __update_ready_state(self):
249+
"""Update the window ready state based on disk selection and FDE passphrase validity."""
250+
if not self.__selected_device:
251+
self.__window.set_ready(False)
252+
return
253+
# If FDE is enabled, passphrase must be valid
254+
try:
255+
if self.fde_row.get_active():
256+
if not self.__validate_passphrase():
257+
self.__window.set_ready(False)
258+
return
259+
except Exception:
260+
pass
157261
self.__window.set_ready(True)
158262

263+
def __on_passphrase_changed(self, entry):
264+
"""Called when either passphrase field changes."""
265+
self.__update_passphrase_validation_ui()
266+
self.__save_fde_settings()
267+
self.__update_ready_state()
159268

160-
def __on_fde_toggled(self, checkbox):
161-
# No passphrase inputs to manage; optionally could trigger readiness update.
162-
# Keep selection gating on disk radio buttons only.
163-
return
269+
def __on_fde_toggled(self, switch, pspec):
270+
"""Enable/disable passphrase fields and TPM row based on FDE selection."""
271+
fde_active = switch.get_active()
272+
try:
273+
# Show/hide encryption-related rows
274+
self.passphrase_entry.set_visible(fde_active)
275+
self.passphrase_confirm_entry.set_visible(fde_active)
276+
self.tpm_row.set_visible(fde_active)
277+
# If FDE is disabled, reset TPM and clear passphrases
278+
if not fde_active:
279+
self.tpm_row.set_active(False)
280+
self.passphrase_entry.set_text("")
281+
self.passphrase_confirm_entry.set_text("")
282+
self.__clear_passphrase_validation_ui()
283+
else:
284+
# Show validation state when FDE is enabled
285+
self.__update_passphrase_validation_ui()
286+
except Exception:
287+
pass
288+
self.__save_fde_settings()
289+
self.__update_ready_state()
290+
291+
def __on_tpm_toggled(self, switch, pspec):
292+
"""Called when TPM auto-unlock is toggled."""
293+
self.__save_fde_settings()

snow_first_setup/views/install_progress.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,16 +148,25 @@ def __run_install(self):
148148
fs = getattr(self.__window, "install_target_fs", None)
149149
image = getattr(self.__window, "install_target_image", None)
150150
fde_enabled = getattr(self.__window, "install_fde_enabled", False)
151-
print("[DEBUG] __run_install params:", device, fs, image, "fde_enabled:", fde_enabled)
151+
fde_passphrase = getattr(self.__window, "install_fde_passphrase", None) or ""
152+
tpm_enabled = getattr(self.__window, "install_tpm_enabled", False)
153+
print("[DEBUG] __run_install params:", device, fs, image, "fde_enabled:", fde_enabled, "tpm_enabled:", tpm_enabled)
152154

153155
if not device or not fs or not image:
154156
GLib.idle_add(self.__mark_finished, False, _("Missing installation parameters."))
155157
return
156158

157159
GLib.idle_add(self.detail_label.set_text, _("Preparing installation…"))
158160

159-
# Build script arguments with FDE parameters
160-
script_args = [image, fs, device, "true" if fde_enabled else "false"]
161+
# Build script arguments: image, filesystem, device, fde, passphrase, tpm2
162+
script_args = [
163+
image,
164+
fs,
165+
device,
166+
"true" if fde_enabled else "false",
167+
fde_passphrase if fde_enabled else "",
168+
"true" if (fde_enabled and tpm_enabled) else "false"
169+
]
161170

162171
# Use streaming script runner to get real-time JSON updates
163172
success = backend.run_script_streaming(

0 commit comments

Comments
 (0)