Refactor installation process and enhance configuration management; add locale detection and autostart support

This commit is contained in:
2026-07-06 00:01:07 +02:00
parent db651dff28
commit 3ee00f2de3
11 changed files with 240 additions and 80 deletions
+77 -41
View File
@@ -1,18 +1,78 @@
import * as p from "@clack/prompts";
import { groups, packages, toDirs } from "./packages.js";
import { detectGpuVendors, driverPackagesFor } from "./gpu.js";
import { detectGpuVendors, driverPackagesFor, type GpuVendor } from "./gpu.js";
import {
backupConflicts,
ensureSddmEnabled,
ensureSudoSession,
ensureYay,
ensureZsh,
ensureZshIsDefaultShell,
installPackages,
run,
stow,
writeAutostartConfig,
writeLocaleConfig,
writeNvidiaEnvConfig,
writeState,
} from "./stow.js";
const CANCELLED = Symbol("cancelled");
async function selectGroupPackages(chosen: Set<string>): Promise<typeof CANCELLED | void> {
for (const group of groups) {
const groupOptions = packages.filter((pkg) => pkg.group === group);
const pick = await p.select({
message: `Choose a ${group}`,
options: groupOptions.map((pkg) => ({ value: pkg.name, label: pkg.name })),
});
if (p.isCancel(pick)) return CANCELLED;
chosen.add(pick as string);
}
}
async function selectRecommendedPackages(chosen: Set<string>): Promise<typeof CANCELLED | void> {
const ungrouped = packages.filter((pkg) => pkg.tier === "recommended" && !pkg.group);
const picks = await p.multiselect({
message: "Choose recommended packages",
options: ungrouped.map((pkg) => ({ value: pkg.name, label: pkg.name })),
initialValues: ungrouped.map((pkg) => pkg.name),
required: false,
});
if (p.isCancel(picks)) return CANCELLED;
for (const name of picks as string[]) chosen.add(name);
}
/** Returns the vendors whose driver packages the user kept (added to `chosen`), or CANCELLED. */
async function selectGpuDriverPackages(chosen: Set<string>): Promise<GpuVendor[] | typeof CANCELLED> {
const vendors = detectGpuVendors();
const options = vendors.flatMap((vendor) =>
driverPackagesFor([vendor]).map((pkg) => ({ value: pkg, label: `${vendor}: ${pkg}` })),
);
if (options.length === 0) return vendors;
const picked = await p.multiselect({
message: "Detected GPU(s) — confirm driver packages to install",
options,
initialValues: options.map((o) => o.value),
required: false,
});
if (p.isCancel(picked)) return CANCELLED;
for (const name of picked) chosen.add(name);
return vendors.filter((vendor) => driverPackagesFor([vendor]).some((pkg) => picked.includes(pkg)));
}
async function maybeReboot(yes: boolean) {
if (yes) {
p.log.info("Rebooting now...");
run("sudo", ["reboot"]);
return;
}
const reboot = await p.confirm({ message: "Reboot now to apply changes?" });
if (!p.isCancel(reboot) && reboot) run("sudo", ["reboot"]);
}
export async function install({ yes = false }: { yes?: boolean } = {}) {
p.intro("Hyprland dotfiles install");
@@ -23,42 +83,10 @@ export async function install({ yes = false }: { yes?: boolean } = {}) {
const chosen = new Set(packages.filter((pkg) => pkg.tier === "required").map((pkg) => pkg.name));
for (const group of groups) {
const groupOptions = packages.filter((pkg) => pkg.group === group);
const pick = await p.select({
message: `Choose a ${group}`,
options: groupOptions.map((pkg) => ({ value: pkg.name, label: pkg.name })),
});
if (p.isCancel(pick)) return p.cancel("Install cancelled.");
chosen.add(pick as string);
}
const ungrouped = packages.filter((pkg) => pkg.tier === "recommended" && !pkg.group);
const picks = await p.multiselect({
message: "Choose recommended packages",
options: ungrouped.map((pkg) => ({ value: pkg.name, label: pkg.name })),
initialValues: ungrouped.map((pkg) => pkg.name),
required: false,
});
if (p.isCancel(picks)) return p.cancel("Install cancelled.");
for (const name of picks as string[]) chosen.add(name);
const gpuVendors = detectGpuVendors();
const gpuOptions = gpuVendors.flatMap((vendor) =>
driverPackagesFor([vendor]).map((pkg) => ({ value: pkg, label: `${vendor}: ${pkg}` })),
);
let gpuPicks: string[] = [];
if (gpuOptions.length > 0) {
const picked = await p.multiselect({
message: "Detected GPU(s) — confirm driver packages to install",
options: gpuOptions,
initialValues: gpuOptions.map((o) => o.value),
required: false,
});
if (p.isCancel(picked)) return p.cancel("Install cancelled.");
gpuPicks = picked as string[];
for (const name of gpuPicks) chosen.add(name);
}
if ((await selectGroupPackages(chosen)) === CANCELLED) return p.cancel("Install cancelled.");
if ((await selectRecommendedPackages(chosen)) === CANCELLED) return p.cancel("Install cancelled.");
const keptGpuVendors = await selectGpuDriverPackages(chosen);
if (keptGpuVendors === CANCELLED) return p.cancel("Install cancelled.");
p.note([...chosen].join("\n"), "Packages to install");
if (!yes) {
@@ -72,12 +100,18 @@ export async function install({ yes = false }: { yes?: boolean } = {}) {
ensureSddmEnabled();
if (chosen.has("zsh")) ensureZsh();
if (gpuVendors.includes("nvidia") && driverPackagesFor(["nvidia"]).some((pkg) => gpuPicks.includes(pkg))) {
writeNvidiaEnvConfig();
if (chosen.has("zsh")) {
ensureZsh();
ensureZshIsDefaultShell();
}
if (keptGpuVendors.includes("nvidia")) writeNvidiaEnvConfig();
const autostartCmds = ["waybar", "ags", "mako", "hyprpaper"].filter((name) => chosen.has(name));
if (chosen.has("hyprlauncher")) autostartCmds.push("hyprlauncher -d");
writeAutostartConfig(autostartCmds);
writeLocaleConfig();
const dirs = toDirs([...chosen]);
const backupDir = backupConflicts(dirs);
if (backupDir) p.log.info(`Backed up conflicting files to ${backupDir}`);
@@ -85,5 +119,7 @@ export async function install({ yes = false }: { yes?: boolean } = {}) {
stow(dirs);
writeState({ dirs });
await maybeReboot(yes);
p.outro("Done.");
}