Merge pull request 'pkgs/clan/lib(install): implement separate nixos-anywhere install phases' (#4710) from ke-install-phases into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4710
This commit is contained in:
hsjobeki
2025-08-12 15:34:15 +00:00
8 changed files with 136 additions and 29 deletions

View File

@@ -4,7 +4,7 @@
&[data-expanded] {
@apply outline-def-2 outline-1 outline;
z-index: var(--z-index + 5);
z-index: calc(var(--z-index) + 5);
}
&[data-highlighted] {

View File

@@ -146,6 +146,7 @@ export const Select = (props: SelectProps) => {
<KSelect.HiddenSelect {...selectProps} />
<KSelect.Trigger
class={cx(styles.trigger)}
style={{ "--z-index": zIndex() }}
data-loading={loading() || undefined}
>
<KSelect.Value<Option>>

View File

@@ -251,6 +251,10 @@ export const Onboarding: Component<RouteSectionProps> = (props) => {
},
}).result;
await client.fetch("create_secrets_user", {
flake_dir: path,
}).result;
if (resp.status === "error") {
setWelcomeError(resp.errors[0].message);
setState("welcome");

View File

@@ -18,29 +18,64 @@ type ResultDataMap = {
[K in OperationNames]: SuccessQuery<K>["data"];
};
export const mockFetcher: Fetcher = <K extends OperationNames>(
const mockFetcher: Fetcher = <K extends OperationNames>(
name: K,
_args: unknown,
): ApiCall<K> => {
// TODO: Make this configurable for every story
const resultData: Partial<ResultDataMap> = {
get_machine_flash_options: {
keymaps: ["DE_de", "US_en"],
languages: ["en", "de"],
},
get_system_file: ["id_rsa.pub"],
list_system_storage_devices: {
blockdevices: [
{
name: "sda_bla_bla",
path: "/dev/sda",
id_link: "sda_bla_bla",
},
{
name: "sdb_foo_foo",
path: "/dev/sdb",
id_link: "sdb_foo_foo",
},
] as SuccessQuery<"list_system_storage_devices">["data"]["blockdevices"],
},
get_machine_disk_schemas: {
"single-disk": {
readme: "This is a single disk installation schema",
frontmatter: {
description: "Single disk installation schema",
},
name: "single-disk",
placeholders: {
mainDisk: {
label: "Main Disk",
required: true,
options: ["disk1", "usb1"],
},
},
},
},
get_generators: [
{
name: "funny.gritty",
prompts: [
{
name: "gritty.name",
description: "Name of the gritty",
prompt_type: "line",
display: {
label: "Gritty Name",
group: "User",
required: true,
},
},
],
},
],
run_generators: true,
get_machine_hardware_summary: {
hardware_config: "nixos-facter",
},
};
return {

View File

@@ -7,7 +7,7 @@ import { StepLayout } from "../../Steps";
const ChoiceLocalOrRemote = () => {
const stepSignal = useStepper<InstallSteps>();
return (
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-3 size-full">
<div class="flex flex-col gap-6 rounded-md px-4 py-6 text-fg-def-1 bg-def-2">
<div class="flex justify-between gap-2">
<div class="flex flex-col justify-center gap-1 px-1">

View File

@@ -16,10 +16,7 @@ import { Alert } from "@/src/components/Alert/Alert";
import { LoadingBar } from "@/src/components/LoadingBar/LoadingBar";
import { Button } from "@/src/components/Button/Button";
import Icon from "@/src/components/Icon/Icon";
import {
useMachineFlashOptions,
useSystemStorageOptions,
} from "@/src/hooks/queries";
import { useSystemStorageOptions } from "@/src/hooks/queries";
import { useApiClient } from "@/src/hooks/ApiClient";
import { onMount } from "solid-js";
@@ -144,8 +141,6 @@ const ConfigureImage = () => {
throw new Error("No data returned from api call");
};
const optionsQuery = useMachineFlashOptions();
let content: Node;
return (
@@ -309,15 +304,17 @@ const FlashProgress = () => {
});
const handleCancel = async () => {
if (store.flash) {
const progress = store.flash.progress;
if (progress) {
await progress.cancel();
}
}
stepSignal.previous();
};
return (
<div class="flex size-full h-60 flex-col items-center justify-end bg-inv-4">
<div class="flex size-full flex-col items-center justify-center bg-inv-4">
<div class="mb-6 flex w-full max-w-md flex-col items-center gap-3 fg-inv-1">
<Typography
hierarchy="title"
@@ -344,7 +341,7 @@ const FlashDone = () => {
const stepSignal = useStepper<InstallSteps>();
return (
<div class="flex size-full flex-col items-center justify-between bg-inv-4">
<div class="flex w-full max-w-md flex-col items-center gap-3 py-6 fg-inv-1">
<div class="flex size-full max-w-md flex-col items-center justify-center gap-3 py-6 fg-inv-1">
<div class="rounded-full bg-semantic-success-4">
<Icon icon="Checkmark" class="size-9" />
</div>

View File

@@ -70,7 +70,6 @@ const ConfigureAddress = () => {
// Here you would typically trigger the ISO creation process
stepSignal.next();
console.log("Shit doesnt work", values);
};
return (
@@ -318,6 +317,25 @@ interface PromptForm extends FieldValues {
promptValues: PromptValues;
}
const sanitize = (name: string) => {
return name.replace(".", "__dot__");
};
const restore = (name: string) => {
return name.replace("__dot__", ".");
};
const transformPromptValues = (
values: PromptValues,
transform: (s: string) => string,
): PromptValues =>
Object.fromEntries(
Object.entries(values).map(([key, value]) => [
transform(key),
Object.fromEntries(
Object.entries(value).map(([k, v]) => [transform(k), v]),
),
]),
);
interface PromptsFieldsProps {
generators: MachineGenerators;
}
@@ -338,8 +356,11 @@ const PromptsFields = (props: PromptsFieldsProps) => {
if (!acc[groupName]) acc[groupName] = { fields: [], name: groupName };
acc[groupName].fields.push({
prompt,
generator: generator.name,
prompt: {
...prompt,
name: sanitize(prompt.name),
},
generator: sanitize(generator.name),
value: prompt.previous_value,
});
}
@@ -351,16 +372,24 @@ const PromptsFields = (props: PromptsFieldsProps) => {
const [formStore, { Form, Field }] = createForm<PromptForm>({
initialValues: {
promptValues: store.install?.promptValues || {},
promptValues: transformPromptValues(
store.install?.promptValues || {},
sanitize,
),
},
});
console.log(groups);
const handleSubmit: SubmitHandler<PromptForm> = (values, event) => {
console.log("vars submitted", values);
const restoredValues: PromptValues = transformPromptValues(
values.promptValues,
restore,
);
set("install", (s) => ({ ...s, promptValues: values.promptValues }));
console.log("vars submitted", restoredValues);
set("install", (s) => ({ ...s, promptValues: restoredValues }));
console.log("vars preloaded");
// Here you would typically trigger the ISO creation process
stepSignal.next();
@@ -545,7 +574,14 @@ const InstallSummary = () => {
);
};
type InstallTopic = "generators" | "upload-secrets" | "nixos-anywhere";
type InstallTopic = [
"generators",
"upload-secrets",
"nixos-anywhere",
"formatting",
"rebooting",
"installing",
][number];
const InstallProgress = () => {
const stepSignal = useStepper<InstallSteps>();
@@ -563,7 +599,7 @@ const InstallProgress = () => {
);
return (
<div class="flex size-full h-60 flex-col items-center justify-end bg-inv-4">
<div class="flex size-full flex-col items-center justify-center bg-inv-4">
<div class="mb-6 flex w-full max-w-md flex-col items-center gap-3 fg-inv-1">
<Typography
hierarchy="title"
@@ -599,6 +635,15 @@ const InstallProgress = () => {
<Match when={installState()?.topic === "nixos-anywhere"}>
Running nixos-anywhere ...
</Match>
<Match when={installState()?.topic === "formatting"}>
Formatting ...
</Match>
<Match when={installState()?.topic === "installing"}>
Installing ...
</Match>
<Match when={installState()?.topic === "rebooting"}>
Rebooting ...
</Match>
</Switch>
</Match>
</Switch>
@@ -625,7 +670,7 @@ const InstallDone = (props: InstallDoneProps) => {
const [store, get] = getStepStore<InstallStoreType>(stepSignal);
return (
<div class="flex w-full flex-col items-center bg-inv-4">
<div class="flex size-full flex-col items-center justify-center bg-inv-4">
<div class="flex w-full max-w-md flex-col items-center gap-3 py-6 fg-inv-1">
<div class="rounded-full bg-semantic-success-4">
<Icon icon="Checkmark" class="size-9" />

View File

@@ -22,7 +22,14 @@ log = logging.getLogger(__name__)
BuildOn = Literal["auto", "local", "remote"]
Step = Literal["generators", "upload-secrets", "nixos-anywhere"]
Step = Literal[
"generators",
"upload-secrets",
"nixos-anywhere",
"formatting",
"rebooting",
"installing",
]
def notify_install_step(current: Step) -> None:
@@ -195,4 +202,22 @@ def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
)
notify_install_step("nixos-anywhere")
run(cmd, RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True))
run(
[*cmd, "--phases", "kexec"],
RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
)
notify_install_step("formatting")
run(
[*cmd, "--phases", "disko"],
RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
)
notify_install_step("installing")
run(
[*cmd, "--phases", "install"],
RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
)
notify_install_step("rebooting")
run(
[*cmd, "--phases", "reboot"],
RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True),
)