Merge pull request #212 from naturallaw777/copilot/update-installer-password-prompt

[WIP] Update installer to show generated login password
This commit is contained in:
Sovran_Systems
2026-04-12 16:38:34 -05:00
committed by GitHub

View File

@@ -5,6 +5,7 @@ gi.require_version("Adw", "1")
from gi.repository import Gtk, Adw, GLib from gi.repository import Gtk, Adw, GLib
import atexit import atexit
import os import os
import secrets
import subprocess import subprocess
import sys import sys
import threading import threading
@@ -36,6 +37,26 @@ DEPLOYED_FLAKE = """\
} }
""" """
DICEWARE_WORDS = [
"apple", "barn", "brook", "cabin", "cedar", "cloud", "coral", "crane",
"delta", "eagle", "ember", "fern", "field", "flame", "flora", "flint",
"frost", "grove", "haven", "hedge", "holly", "heron", "jade", "juniper",
"kelp", "larch", "lemon", "lilac", "linden", "loch", "lotus", "maple",
"marsh", "meadow", "mist", "mossy", "mount", "oak", "ocean", "olive",
"petal", "pine", "pixel", "plum", "pond", "prism", "quartz", "raven",
"ridge", "river", "robin", "rocky", "rose", "rowan", "sage", "sand",
"sierra", "silver", "slate", "snow", "solar", "spark", "spruce", "stone",
"storm", "summit", "swift", "thorn", "tide", "timber", "torch", "trout",
"vale", "vault", "vine", "walnut", "wave", "willow", "wren", "amber",
"aspen", "birch", "blaze", "bloom", "bluff", "coast", "copper", "crest",
"dune", "elder", "fjord", "forge", "glade", "glen", "glow", "gulf",
]
def generate_diceware_password():
words = [secrets.choice(DICEWARE_WORDS) for _ in range(3)]
digit = secrets.randbelow(10)
return "-".join(words) + f"-{digit}"
try: try:
logfile = open(LOG, "a") logfile = open(LOG, "a")
atexit.register(logfile.close) atexit.register(logfile.close)
@@ -135,6 +156,7 @@ class InstallerWindow(Adw.ApplicationWindow):
self.boot_size = None self.boot_size = None
self.data_disk = None self.data_disk = None
self.data_size = None self.data_size = None
self.free_password = None
# Root navigation view # Root navigation view
self.nav = Adw.NavigationView() self.nav = Adw.NavigationView()
@@ -970,6 +992,46 @@ class InstallerWindow(Adw.ApplicationWindow):
run_stream(["sudo", "nix", "--extra-experimental-features", "nix-command flakes", run_stream(["sudo", "nix", "--extra-experimental-features", "nix-command flakes",
"flake", "lock", "/mnt/etc/nixos"], buf) "flake", "lock", "/mnt/etc/nixos"], buf)
# Generate diceware passwords and write them to the installed system
GLib.idle_add(append_text, buf, "Setting up user passwords...\n")
self.free_password = generate_diceware_password()
root_password = generate_diceware_password()
run(["sudo", "mkdir", "-p", "/mnt/var/lib/secrets"])
run(["sudo", "chmod", "700", "/mnt/var/lib/secrets"])
proc = subprocess.run(
["sudo", "tee", "/mnt/var/lib/secrets/free-password"],
input=self.free_password, capture_output=True, text=True
)
if proc.returncode != 0:
log(proc.stderr)
raise RuntimeError(proc.stderr.strip() or "Failed to write free-password")
run(["sudo", "chmod", "600", "/mnt/var/lib/secrets/free-password"])
proc = subprocess.run(
["sudo", "tee", "/mnt/var/lib/secrets/root-password"],
input=root_password, capture_output=True, text=True
)
if proc.returncode != 0:
log(proc.stderr)
raise RuntimeError(proc.stderr.strip() or "Failed to write root-password")
run(["sudo", "chmod", "600", "/mnt/var/lib/secrets/root-password"])
proc = subprocess.run(
["sudo", "chroot", "/mnt", "/run/current-system/sw/bin/chpasswd"],
input=f"free:{self.free_password}\nroot:{root_password}",
capture_output=True, text=True
)
if proc.returncode != 0:
proc = subprocess.run(
["sudo", "chroot", "/mnt", "chpasswd"],
input=f"free:{self.free_password}\nroot:{root_password}",
capture_output=True, text=True
)
if proc.returncode != 0:
log(proc.stderr)
raise RuntimeError(proc.stderr.strip() or "Failed to set passwords via chpasswd")
GLib.idle_add(self.push_complete) GLib.idle_add(self.push_complete)
# ── Complete ─────────────────────────────────────────────────────────── # ── Complete ───────────────────────────────────────────────────────────
@@ -979,14 +1041,51 @@ class InstallerWindow(Adw.ApplicationWindow):
status = Adw.StatusPage() status = Adw.StatusPage()
status.set_title("Installation Complete!") status.set_title("Installation Complete!")
status.set_description("Rebooting…") status.set_description("Before rebooting, write down your login password.")
status.set_vexpand(True) status.set_icon_name("dialog-password-symbolic")
outer.append(status) outer.append(status)
self.push_page("Complete", outer) pw_frame = Gtk.Frame()
pw_frame.set_margin_start(60)
pw_frame.set_margin_end(60)
pw_label = Gtk.Label()
pw_label.set_markup(
f"<span font_family='monospace' size='xx-large' weight='bold'>"
f"{GLib.markup_escape_text(self.free_password)}</span>"
)
pw_label.set_selectable(True)
pw_label.set_margin_top(16)
pw_label.set_margin_bottom(16)
pw_frame.set_child(pw_label)
outer.append(pw_frame)
GLib.timeout_add_seconds(3, lambda: subprocess.run(["sudo", "reboot"])) warning = Gtk.Label()
warning.set_markup(
"<span foreground='#e8a838' size='medium' weight='bold'>"
"⚠ Write this password down now.\n"
"You will need it to log in to your computer and the Sovran Hub.\n"
"This password cannot be recovered.</span>"
)
warning.set_justify(Gtk.Justification.CENTER)
warning.set_wrap(True)
warning.set_margin_top(20)
warning.set_margin_start(48)
warning.set_margin_end(48)
outer.append(warning)
outer.append(Gtk.Label(label="", vexpand=True))
btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
btn_box.set_halign(Gtk.Align.CENTER)
btn_box.set_margin_bottom(32)
reboot_btn = Gtk.Button(label="I Have Written Down My Password — Reboot Now")
reboot_btn.add_css_class("suggested-action")
reboot_btn.add_css_class("pill")
reboot_btn.connect("clicked", lambda b: subprocess.run(["sudo", "reboot"]))
btn_box.append(reboot_btn)
outer.append(btn_box)
self.push_page("Complete", outer)
return False return False
# ── Error screen ─────────────────────────────────────────────────────── # ── Error screen ───────────────────────────────────────────────────────