From a1c640db3d568b272fcf78360645a7548408741f Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sun, 29 Dec 2024 13:00:40 +0100 Subject: [PATCH 1/2] UI: fix installer workflow asking for sudo pw in background --- pkgs/clan-cli/clan_cli/flash/automount.py | 11 +- pkgs/clan-cli/clan_cli/flash/flash.py | 10 +- .../app/src/components/modal/index.tsx | 7 +- pkgs/webview-ui/app/src/routes/flash/view.tsx | 102 +++++++++++------- 4 files changed, 83 insertions(+), 47 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/flash/automount.py b/pkgs/clan-cli/clan_cli/flash/automount.py index af2295f0d..a24b31b4c 100644 --- a/pkgs/clan-cli/clan_cli/flash/automount.py +++ b/pkgs/clan-cli/clan_cli/flash/automount.py @@ -7,13 +7,14 @@ from pathlib import Path from clan_cli.cmd import Log, RunOpts, run from clan_cli.errors import ClanError from clan_cli.machines.machines import Machine +from clan_cli.nix import nix_shell log = logging.getLogger(__name__) @contextmanager def pause_automounting( - devices: list[Path], machine: Machine + devices: list[Path], machine: Machine, sudo: str = "pkexec" ) -> Generator[None, None, None]: """ Pause automounting on the device for the duration of this context @@ -32,7 +33,11 @@ def pause_automounting( raise ClanError(msg) str_devs = [str(dev) for dev in devices] - cmd = ["sudo", str(inhibit_path), "enable", *str_devs] + cmd = nix_shell( + ["nixpkgs#pkexec"] if sudo == "pkexec" else [], + [sudo, str(inhibit_path), "enable", *str_devs], + ) + result = run( cmd, RunOpts( @@ -42,7 +47,7 @@ def pause_automounting( if result.returncode != 0: machine.error("Failed to inhibit automounting") yield None - cmd = ["sudo", str(inhibit_path), "disable", *str_devs] + cmd = [sudo, str(inhibit_path), "disable", *str_devs] result = run(cmd, RunOpts(log=Log.BOTH, check=False, prefix=machine.name)) if result.returncode != 0: machine.error("Failed to re-enable automounting") diff --git a/pkgs/clan-cli/clan_cli/flash/flash.py b/pkgs/clan-cli/clan_cli/flash/flash.py index 8b0002372..4fc39d84c 100644 --- a/pkgs/clan-cli/clan_cli/flash/flash.py +++ b/pkgs/clan-cli/clan_cli/flash/flash.py @@ -47,9 +47,11 @@ def flash_machine( write_efi_boot_entries: bool, debug: bool, extra_args: list[str] | None = None, + use_user_permission: bool = False, ) -> None: devices = [Path(disk.device) for disk in disks] - with pause_automounting(devices, machine): + sudo = "pkexec" if use_user_permission else "sudo" + with pause_automounting(devices, machine, sudo): if extra_args is None: extra_args = [] system_config_nix: dict[str, Any] = {} @@ -108,10 +110,12 @@ def flash_machine( disko_install = [] if os.geteuid() != 0: + # Use pkexec to elevate permissions if not running as root + perm_prefix = "pkexec" if use_user_permission else "exec sudo" if shutil.which("sudo") is None: msg = "sudo is required to run disko-install as a non-root user" raise ClanError(msg) - wrapper = 'set -x; disko_install=$(command -v disko-install); exec sudo "$disko_install" "$@"' + wrapper = f'set -x; disko_install=$(command -v disko-install); {perm_prefix} "$disko_install" "$@"' disko_install.extend(["bash", "-c", wrapper]) disko_install.append("disko-install") @@ -124,6 +128,8 @@ def flash_machine( for disk in disks: disko_install.extend(["--disk", disk.name, disk.device]) + log.info("Will flash disk %s: %s", disk.name, disk.device) + disko_install.extend(["--extra-files", str(local_dir), upload_dir]) disko_install.extend(["--flake", str(machine.flake) + "#" + machine.name]) disko_install.extend(["--mode", str(mode)]) diff --git a/pkgs/webview-ui/app/src/components/modal/index.tsx b/pkgs/webview-ui/app/src/components/modal/index.tsx index 3542c28fe..c6869fe67 100644 --- a/pkgs/webview-ui/app/src/components/modal/index.tsx +++ b/pkgs/webview-ui/app/src/components/modal/index.tsx @@ -1,5 +1,5 @@ import Dialog from "corvu/dialog"; -import { createEffect, createSignal, JSX } from "solid-js"; +import { createSignal, JSX } from "solid-js"; import { Button } from "../button"; import Icon from "../icon"; import cx from "classnames"; @@ -13,7 +13,7 @@ interface ModalProps { export const Modal = (props: ModalProps) => { const [dragging, setDragging] = createSignal(false); const [startOffset, setStartOffset] = createSignal({ x: 0, y: 0 }); - // const [dialogStyle, setDialogStyle] = createSignal({ top: 100, left: 100 }); + let dialogRef: HTMLDivElement; const handleMouseDown = (e: MouseEvent) => { @@ -37,9 +37,6 @@ export const Modal = (props: ModalProps) => { const handleMouseUp = () => setDragging(false); - createEffect(() => { - console.log("dialog open", props.open); - }); return ( diff --git a/pkgs/webview-ui/app/src/routes/flash/view.tsx b/pkgs/webview-ui/app/src/routes/flash/view.tsx index 257953729..c733af3fb 100644 --- a/pkgs/webview-ui/app/src/routes/flash/view.tsx +++ b/pkgs/webview-ui/app/src/routes/flash/view.tsx @@ -14,6 +14,7 @@ import { FieldValues, setValue, getValue, + getValues, } from "@modular-forms/solid"; import { createQuery } from "@tanstack/solid-query"; import { createEffect, createSignal, For, Show } from "solid-js"; @@ -146,63 +147,89 @@ export const Flash = () => { return dataTransfer.files; }; const [confirmOpen, setConfirmOpen] = createSignal(false); + const [isFlashing, setFlashing] = createSignal(false); - const handleConfirm = async (values: FlashFormValues) => { + const handleSubmit = (values: FlashFormValues) => { setConfirmOpen(true); - console.log("Submit:", values); - toast.error("Not fully implemented yet"); - // Disabled for now. To prevent accidental flashing of local disks - // User should confirm the disk to flash to - - // try { - // await callApi("flash_machine", { - // machine: { - // name: values.machine.devicePath, - // flake: { - // loc: values.machine.flake, - // }, - // }, - // mode: "format", - // disks: [{ name: "main", device: values.disk }], - // system_config: { - // language: values.language, - // keymap: values.keymap, - // ssh_keys_path: values.sshKeys.map((file) => file.name), - // }, - // dry_run: false, - // write_efi_boot_entries: false, - // debug: false, - // }); - // } catch (error) { - // toast.error(`Error could not flash disk: ${error}`); - // console.error("Error submitting form:", error); - // } + }; + const handleConfirm = async () => { + // Wait for the flash to complete + const values = getValues(formStore) as FlashFormValues; + setFlashing(true); + console.log("Confirmed flash:", values); + try { + await toast.promise( + callApi("flash_machine", { + machine: { + name: values.machine.devicePath, + flake: { + loc: values.machine.flake, + }, + }, + mode: "format", + disks: [{ name: "main", device: values.disk }], + system_config: { + language: values.language, + keymap: values.keymap, + ssh_keys_path: values.sshKeys.map((file) => file.name), + }, + dry_run: false, + write_efi_boot_entries: false, + debug: false, + use_pkexec: true, + }), + { + error: (errors) => `Error flashing disk: ${errors}`, + loading: "Flashing ... This may take up to 15minutes.", + success: "Disk flashed successfully", + }, + ); + } catch (error) { + toast.error(`Error could not flash disk: ${error}`); + } finally { + setFlashing(false); + } + setConfirmOpen(false); }; return ( <>
setConfirmOpen(false)} + open={confirmOpen() || isFlashing()} + handleClose={() => !isFlashing() && setConfirmOpen(false)} title="Confirm" >
-
+
- Warning: All data on will be lost. -
+ Warning: All data will be lost. +
+ Selected disk: '{getValue(formStore, "disk")}'
- - + +
@@ -214,7 +241,7 @@ export const Flash = () => { Will make bootstrapping new machines easier by providing secure remote connection to any machine when plugged in. -
+
{(field, props) => ( @@ -259,6 +286,7 @@ export const Flash = () => { labelProps={{ labelAction: (