Enhance installation process with terminal selection, update autostart commands, and add PolyForm license; remove unused AGS config
This commit is contained in:
+21
-3
@@ -2,6 +2,7 @@ import * as p from "@clack/prompts";
|
||||
import { groups, packages, toDirs } from "./packages.js";
|
||||
import { detectGpuVendors, driverPackagesFor, type GpuVendor } from "./gpu.js";
|
||||
import {
|
||||
autostartCmdsFor,
|
||||
backupConflicts,
|
||||
ensureSddmEnabled,
|
||||
ensureSudoSession,
|
||||
@@ -15,9 +16,11 @@ import {
|
||||
writeLocaleConfig,
|
||||
writeNvidiaEnvConfig,
|
||||
writeState,
|
||||
writeTerminalConfig,
|
||||
} from "./stow.js";
|
||||
|
||||
const CANCELLED = Symbol("cancelled");
|
||||
const TERMINAL_CANDIDATES = ["kitty", "foot"];
|
||||
|
||||
async function selectGroupPackages(chosen: Set<string>): Promise<typeof CANCELLED | void> {
|
||||
for (const group of groups) {
|
||||
@@ -63,6 +66,20 @@ async function selectGpuDriverPackages(chosen: Set<string>): Promise<GpuVendor[]
|
||||
return vendors.filter((vendor) => driverPackagesFor([vendor]).some((pkg) => picked.includes(pkg)));
|
||||
}
|
||||
|
||||
/** null means no terminal was chosen — the SUPER+T keybind is skipped. */
|
||||
async function selectMainTerminal(chosen: Set<string>): Promise<string | null | typeof CANCELLED> {
|
||||
const candidates = TERMINAL_CANDIDATES.filter((name) => chosen.has(name));
|
||||
if (candidates.length === 0) return null;
|
||||
if (candidates.length === 1) return candidates[0];
|
||||
|
||||
const picked = await p.select({
|
||||
message: "Choose your main terminal (bound to SUPER+T)",
|
||||
options: candidates.map((name) => ({ value: name, label: name })),
|
||||
});
|
||||
if (p.isCancel(picked)) return CANCELLED;
|
||||
return picked;
|
||||
}
|
||||
|
||||
async function maybeReboot(yes: boolean) {
|
||||
if (yes) {
|
||||
p.log.info("Rebooting now...");
|
||||
@@ -85,6 +102,8 @@ export async function install({ yes = false }: { yes?: boolean } = {}) {
|
||||
|
||||
if ((await selectGroupPackages(chosen)) === CANCELLED) return p.cancel("Install cancelled.");
|
||||
if ((await selectRecommendedPackages(chosen)) === CANCELLED) return p.cancel("Install cancelled.");
|
||||
const mainTerminal = await selectMainTerminal(chosen);
|
||||
if (mainTerminal === CANCELLED) return p.cancel("Install cancelled.");
|
||||
const keptGpuVendors = await selectGpuDriverPackages(chosen);
|
||||
if (keptGpuVendors === CANCELLED) return p.cancel("Install cancelled.");
|
||||
|
||||
@@ -107,10 +126,9 @@ export async function install({ yes = false }: { yes?: boolean } = {}) {
|
||||
|
||||
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);
|
||||
writeAutostartConfig(autostartCmdsFor([...chosen]));
|
||||
writeLocaleConfig();
|
||||
if (mainTerminal) writeTerminalConfig(mainTerminal);
|
||||
|
||||
const dirs = toDirs([...chosen]);
|
||||
const backupDir = backupConflicts(dirs);
|
||||
|
||||
@@ -15,7 +15,6 @@ export const packages: Pkg[] = [
|
||||
{ name: "stow", tier: "required" },
|
||||
{ name: "ttf-0xproto-nerd", tier: "required" },
|
||||
{ name: "waybar", tier: "recommended", group: "bar" },
|
||||
{ name: "ags", tier: "recommended", group: "bar", aur: true },
|
||||
{ name: "hyprlauncher", tier: "recommended", group: "launcher" },
|
||||
{ name: "kitty", tier: "recommended" },
|
||||
{ name: "foot", tier: "recommended" },
|
||||
|
||||
+43
-8
@@ -1,4 +1,4 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import {
|
||||
existsSync,
|
||||
lstatSync,
|
||||
@@ -36,13 +36,22 @@ export function run(cmd: string, args: string[], options: RunOptions = {}) {
|
||||
|
||||
export function ensureSudoSession() {
|
||||
execFileSync("sudo", ["-v"], { stdio: "inherit" });
|
||||
setInterval(() => {
|
||||
try {
|
||||
execFileSync("sudo", ["-n", "-v"]);
|
||||
} catch {
|
||||
// credential expired or revoked; the next real sudo call will re-prompt
|
||||
}
|
||||
}, 60_000).unref();
|
||||
// A JS setInterval can't refresh this during a long synchronous step (e.g. makepkg's
|
||||
// multi-minute build in ensureYay) — execFileSync blocks the entire single-threaded event
|
||||
// loop, so the timer callback never gets a chance to run and the ticket silently expires
|
||||
// mid-build. Spawn the refresh loop as a real child process instead, so it keeps running
|
||||
// regardless of what our own process is blocked on. It's deliberately NOT `detached` —
|
||||
// sudo's default tty-scoped ticket is tied to the session/controlling-tty, and `detached`
|
||||
// calls setsid() on Linux, which moves the child to a new session and makes its `sudo -n -v`
|
||||
// fail to match our ticket (verified empirically: a setsid'd sudo call can't reuse a ticket
|
||||
// established outside it, while a plain background child in the same session can). It exits
|
||||
// on its own once this process (checked via `kill -0`) is gone.
|
||||
const child = spawn(
|
||||
"sh",
|
||||
["-c", `while kill -0 ${process.pid} 2>/dev/null; do sleep 60; sudo -n -v 2>/dev/null; done`],
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
child.unref();
|
||||
}
|
||||
|
||||
function commandExists(cmd: string): boolean {
|
||||
@@ -60,6 +69,14 @@ export function ensureYay() {
|
||||
const tmp = mkdtempSync(join(tmpdir(), "yay-bootstrap-"));
|
||||
try {
|
||||
run("git", ["clone", "https://aur.archlinux.org/yay.git", tmp]);
|
||||
// makepkg's own root pacman calls are always `sudo -k ...` (confirmed by reading
|
||||
// /usr/bin/makepkg) — per sudo(8), `-k` combined with a command deliberately ignores any
|
||||
// cached credential and forces a fresh password prompt. This is upstream makepkg's own
|
||||
// design, not something ensureSudoSession's ticket caching can avoid, and it happens once
|
||||
// per root escalation makepkg performs (dependency install, then package install), so a
|
||||
// clean install (yay never already present) will ask for the password again here — possibly
|
||||
// more than once.
|
||||
console.log("Building yay from source (one-time) — you may be asked for your password again during this step.");
|
||||
run("makepkg", ["-si", "--noconfirm"], { cwd: tmp });
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
@@ -136,6 +153,24 @@ export function writeAutostartConfig(execCmds: string[]) {
|
||||
writeFileSync(join(dir, "autostart.lua"), `hl.on("hyprland.start", function()\n${body}\nend)\n`);
|
||||
}
|
||||
|
||||
// Autostart commands for whichever of these package names are present. Assumes chosen
|
||||
// package names == state.dirs (true today: no package sets a custom `dir`). If a future
|
||||
// package needs dir != name AND is autostart-relevant, store chosen names in state.json too.
|
||||
export function autostartCmdsFor(names: string[]): string[] {
|
||||
const set = new Set(names);
|
||||
const cmds = ["waybar", "mako", "hyprpaper"].filter((name) => set.has(name));
|
||||
if (set.has("hyprlauncher")) cmds.push("hyprlauncher -d");
|
||||
return cmds;
|
||||
}
|
||||
|
||||
// SUPER+T's target — the static hyprland.lua no longer hardcodes a terminal, since kitty/foot
|
||||
// are both optional and either, both, or neither may be installed.
|
||||
export function writeTerminalConfig(terminal: string) {
|
||||
const dir = hyprConfD();
|
||||
mkdirSync(dir, { recursive: true });
|
||||
writeFileSync(join(dir, "terminal.lua"), `hl.bind("SUPER + T", hl.dsp.exec_cmd("${terminal}"))\n`);
|
||||
}
|
||||
|
||||
export function writeLocaleConfig() {
|
||||
const dir = hyprConfD();
|
||||
mkdirSync(dir, { recursive: true });
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import { ensureSudoSession, readState, restow, run } from "./stow.js";
|
||||
import { autostartCmdsFor, ensureSudoSession, readState, restow, run, writeAutostartConfig } from "./stow.js";
|
||||
|
||||
export async function update({ yes = false }: { yes?: boolean } = {}) {
|
||||
p.intro("Hyprland dotfiles update");
|
||||
@@ -18,5 +18,6 @@ export async function update({ yes = false }: { yes?: boolean } = {}) {
|
||||
run("sudo", ["pacman", "-Syu"]);
|
||||
run("yay", ["-Syu"]);
|
||||
restow(dirs);
|
||||
writeAutostartConfig(autostartCmdsFor(dirs));
|
||||
p.outro("Done.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user