-
Notifications
You must be signed in to change notification settings - Fork 1
feat: support cayo/snowdrift and root password #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4b841c7
4ccbf1a
4fbaaf4
e926752
ae78412
97b60ee
e8292cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,24 @@ | ||
| { | ||
| "images": [ | ||
| { | ||
| "snow": { | ||
| { | ||
| "target": "ghcr.io/frostyard/snow:latest", | ||
| "description": "standard", | ||
| "display_name": "Snow Linux" | ||
| }, | ||
| "snowfield": { | ||
| { | ||
| "target": "ghcr.io/frostyard/snowfield:latest", | ||
| "description": "Surface", | ||
| "display_name": "Snowfield Linux" | ||
| }, | ||
| { | ||
| "target": "ghcr.io/frostyard/snowdrift:latest", | ||
| "description": "server", | ||
| "display_name": "Snowdrift Server" | ||
| }, | ||
| { | ||
| "target": "ghcr.io/frostyard/cayo:latest", | ||
| "description": "server", | ||
| "display_name": "Cayo Server" | ||
| } | ||
| } | ||
| ] | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,9 @@ class VanillaInstallConfirm(Adw.Bin): | |
| fs_label = Gtk.Template.Child() | ||
| fde_label = Gtk.Template.Child() | ||
| image_combo = Gtk.Template.Child() | ||
| root_password_group = Gtk.Template.Child() | ||
| root_password_entry = Gtk.Template.Child() | ||
| root_password_confirm_entry = Gtk.Template.Child() | ||
| confirm_checkbox = Gtk.Template.Child() | ||
| cancel_button = Gtk.Template.Child() | ||
|
|
||
|
|
@@ -28,11 +31,15 @@ def __init__(self, window, **kwargs): | |
| self.__image_text = "" | ||
| self.__image_target = None | ||
| self.__confirm_checked = False | ||
| self.__root_password = "" | ||
| self.__root_password_confirm = "" | ||
|
|
||
| # connect signals to update readiness | ||
| try: | ||
| self.confirm_checkbox.connect("toggled", self.__on_input_changed) | ||
| self.cancel_button.connect("clicked", self.__on_cancel_clicked) | ||
| self.root_password_entry.connect("changed", self.__on_input_changed) | ||
| self.root_password_confirm_entry.connect("changed", self.__on_input_changed) | ||
| except Exception: | ||
| pass | ||
|
|
||
|
|
@@ -78,6 +85,15 @@ def set_page_active(self): | |
| pass | ||
| self.__confirm_checked = False | ||
|
|
||
| # Clear password fields | ||
| try: | ||
| self.root_password_entry.set_text("") | ||
| self.root_password_confirm_entry.set_text("") | ||
| self.__root_password = "" | ||
| self.__root_password_confirm = "" | ||
| except Exception: | ||
|
||
| pass | ||
|
|
||
| # Trigger __on_input_changed to populate __image_target from current combo selection | ||
| self.__on_input_changed() | ||
|
|
||
|
|
@@ -115,15 +131,61 @@ def __on_input_changed(self, *args): | |
| except Exception: | ||
| self.__confirm_checked = False | ||
|
|
||
| # Update password fields visibility and cache passwords | ||
| self.__update_password_visibility() | ||
| try: | ||
| self.__root_password = self.root_password_entry.get_text() | ||
| self.__root_password_confirm = self.root_password_confirm_entry.get_text() | ||
| except Exception: | ||
| self.__root_password = "" | ||
| self.__root_password_confirm = "" | ||
|
Comment on lines
+139
to
+141
|
||
|
|
||
| print(f"[DEBUG] Final state: image_target={self.__image_target}, confirm={self.__confirm_checked}") | ||
| self.__validate() | ||
|
|
||
| def __update_password_visibility(self): | ||
| """Show password fields only for specific images that require a root password""" | ||
| try: | ||
| # Define which image targets require a root password (canonical, lowercased IDs) | ||
| password_required_targets = ("cayo", "snowdrift") | ||
|
|
||
| needs_root_password = False | ||
| target_lower = None | ||
| text_lower = None | ||
|
|
||
| if self.__image_target: | ||
| target_lower = self.__image_target.lower() | ||
|
|
||
| if self.__image_text: | ||
| text_lower = self.__image_text.lower() | ||
|
|
||
| # Prefer the canonical target identifier if available; fall back to display text. | ||
| if target_lower is not None: | ||
| needs_root_password = target_lower in password_required_targets | ||
| elif text_lower is not None: | ||
| needs_root_password = text_lower in password_required_targets | ||
| self.root_password_group.set_visible(needs_root_password) | ||
| print(f"[DEBUG] Root password visibility: {needs_root_password} (target={self.__image_target}, text={self.__image_text})") | ||
| except Exception as e: | ||
| print(f"[DEBUG] Exception in __update_password_visibility: {e}") | ||
|
|
||
| def __validate(self): | ||
| # enable Next only if image target specified and confirmation checked | ||
| ok = False | ||
| try: | ||
| ok = bool(self.__image_target) and self.__confirm_checked and self.__image_target != _("No images found") | ||
| except Exception: | ||
|
|
||
| # If root password is visible, validate that passwords match and are not empty | ||
| if ok and self.root_password_group.get_visible(): | ||
| passwords_valid = ( | ||
| self.__root_password and | ||
| self.__root_password_confirm and | ||
| self.__root_password == self.__root_password_confirm | ||
| ) | ||
| ok = ok and passwords_valid | ||
|
Comment on lines
+180
to
+185
|
||
| print(f"[DEBUG] Password validation: passwords_valid={passwords_valid}, pw_len={len(self.__root_password)}, confirm_len={len(self.__root_password_confirm)}, match={self.__root_password == self.__root_password_confirm}") | ||
| except Exception as e: | ||
| print(f"[DEBUG] Exception in __validate: {e}") | ||
| ok = False | ||
| self.__window.set_ready(ok) | ||
|
|
||
|
|
@@ -148,8 +210,16 @@ def finish(self): | |
| try: | ||
| self.__window.install_target_image = image | ||
| print(f"[DEBUG] Successfully set window.install_target_image = {image}") | ||
|
|
||
| # Store root password if it was collected | ||
| if self.root_password_group.get_visible() and self.__root_password: | ||
| self.__window.install_root_password = self.__root_password | ||
|
Comment on lines
+215
to
+216
|
||
| print(f"[DEBUG] Successfully set window.install_root_password (length={len(self.__root_password)})") | ||
| else: | ||
| self.__window.install_root_password = None | ||
| print(f"[DEBUG] No root password needed or collected") | ||
| except Exception as e: | ||
| print("[exception] Failed to set install_target_image:", e) | ||
| print("[exception] Failed to set install parameters:", e) | ||
| pass | ||
| return True | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
| import json | ||
| import threading | ||
| import time | ||
| import tempfile | ||
| import os | ||
|
|
||
| from gi.repository import Gtk, Adw, GLib, Gio | ||
|
|
||
|
|
@@ -27,6 +29,7 @@ def __init__(self, window, **kwargs): | |
| self.__current_step = 0 | ||
| self.__total_steps = 0 | ||
| self.__has_progress = False # True when we have percentage-based progress | ||
| self.__root_password_file = None # Track temp file for cleanup | ||
|
|
||
| # Debug: verify resource presence and child realization | ||
| try: | ||
|
|
@@ -150,22 +153,41 @@ def __run_install(self): | |
| fde_enabled = getattr(self.__window, "install_fde_enabled", False) | ||
| fde_passphrase = getattr(self.__window, "install_fde_passphrase", None) or "" | ||
| tpm_enabled = getattr(self.__window, "install_tpm_enabled", False) | ||
| print("[DEBUG] __run_install params:", device, fs, image, "fde_enabled:", fde_enabled, "tpm_enabled:", tpm_enabled) | ||
| root_password = getattr(self.__window, "install_root_password", None) | ||
| print("[DEBUG] __run_install params:", device, fs, image, "fde_enabled:", fde_enabled, "tpm_enabled:", tpm_enabled, "root_password:", bool(root_password)) | ||
|
|
||
| if not device or not fs or not image: | ||
| GLib.idle_add(self.__mark_finished, False, _("Missing installation parameters.")) | ||
| return | ||
|
|
||
| GLib.idle_add(self.detail_label.set_text, _("Preparing installation…")) | ||
|
|
||
| # Build script arguments: image, filesystem, device, fde, passphrase, tpm2 | ||
| # Handle root password: create temp file if password is set, otherwise use /dev/null | ||
| root_password_path = "/dev/null" | ||
| if root_password: | ||
| try: | ||
| # Create a temporary file and immediately harden its permissions | ||
| fd, root_password_path = tempfile.mkstemp(prefix="snow-root-pw-", text=True) | ||
| os.fchmod(fd, 0o600) | ||
| self.__root_password_file = root_password_path | ||
| # Write password to file and ensure the descriptor is closed | ||
| with os.fdopen(fd, "w", encoding="utf-8") as f: | ||
| f.write(root_password) | ||
| print(f"[DEBUG] Created root password file: {root_password_path}") | ||
| except Exception as e: | ||
| print(f"[ERROR] Failed to create root password file: {e}") | ||
| GLib.idle_add(self.__mark_finished, False, _("Failed to prepare root password.")) | ||
| return | ||
|
|
||
| # Build script arguments: image, filesystem, device, fde, passphrase, tpm2, root_password_file | ||
| script_args = [ | ||
| image, | ||
| fs, | ||
| device, | ||
| "true" if fde_enabled else "false", | ||
| fde_passphrase if fde_enabled else "", | ||
| "true" if (fde_enabled and tpm_enabled) else "false" | ||
| "true" if (fde_enabled and tpm_enabled) else "false", | ||
| root_password_path | ||
| ] | ||
|
|
||
| # Use streaming script runner to get real-time JSON updates | ||
|
|
@@ -176,6 +198,15 @@ def __run_install(self): | |
| line_callback=self.__handle_json_line | ||
| ) | ||
|
|
||
| # Clean up root password file if created | ||
| if self.__root_password_file and os.path.exists(self.__root_password_file): | ||
| try: | ||
| os.unlink(self.__root_password_file) | ||
| print(f"[DEBUG] Cleaned up root password file: {self.__root_password_file}") | ||
| self.__root_password_file = None | ||
| except Exception as e: | ||
| print(f"[WARNING] Failed to clean up root password file: {e}") | ||
|
Comment on lines
+201
to
+208
|
||
|
|
||
| # Handle Snowfield image special case | ||
| if success and "snowfield" in image: | ||
| print("[DEBUG] __run_install: Snowfield image selected, importing Surface Linux secure boot key") | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.