fix: soften windows daemon install

This commit is contained in:
Peter Steinberger
2026-01-17 20:12:23 +00:00
parent 1309fc1f48
commit be12b0771c
4 changed files with 98 additions and 18 deletions

View File

@@ -42,6 +42,28 @@ const STEP_LABELS: Record<string, string> = {
type UpdateChannel = "stable" | "beta";
const DEFAULT_UPDATE_CHANNEL: UpdateChannel = "stable";
const UPDATE_QUIPS = [
"Leveled up! New skills unlocked. You're welcome.",
"Fresh code, same lobster. Miss me?",
"Back and better. Did you even notice I was gone?",
"Update complete. I learned some new tricks while I was out.",
"Upgraded! Now with 23% more sass.",
"I've evolved. Try to keep up.",
"New version, who dis? Oh right, still me but shinier.",
"Patched, polished, and ready to pinch. Let's go.",
"The lobster has molted. Harder shell, sharper claws.",
"Update done! Check the changelog or just trust me, it's good.",
"Reborn from the boiling waters of npm. Stronger now.",
"I went away and came back smarter. You should try it sometime.",
"Update complete. The bugs feared me, so they left.",
"New version installed. Old version sends its regards.",
"Firmware fresh. Brain wrinkles: increased.",
"I've seen things you wouldn't believe. Anyway, I'm updated.",
"Back online. The changelog is long but our friendship is longer.",
"Upgraded! Peter fixed stuff. Blame him if it breaks.",
"Molting complete. Please don't look at my soft shell phase.",
"Version bump! Same chaos energy, fewer crashes (probably).",
];
function normalizeChannel(value?: string | null): UpdateChannel | null {
if (!value) return null;
@@ -61,6 +83,10 @@ function channelToTag(channel: UpdateChannel): string {
return channel === "beta" ? "beta" : "latest";
}
function pickUpdateQuip(): string {
return UPDATE_QUIPS[Math.floor(Math.random() * UPDATE_QUIPS.length)] ?? "Update complete.";
}
function normalizeVersionTag(tag: string): string | null {
const trimmed = tag.trim();
if (!trimmed) return null;
@@ -402,6 +428,10 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
);
}
}
if (!opts.json) {
defaultRuntime.log(theme.muted(pickUpdateQuip()));
}
}
export function registerUpdateCli(program: Command) {

View File

@@ -72,6 +72,7 @@ export async function maybeInstallDaemon(params: {
}
if (shouldInstall) {
let installError: unknown | null = null;
await withProgress(
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
async (progress) => {
@@ -117,16 +118,33 @@ export async function maybeInstallDaemon(params: {
});
progress.setLabel("Installing Gateway daemon…");
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment,
});
progress.setLabel("Gateway daemon installed.");
try {
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment,
});
progress.setLabel("Gateway daemon installed.");
} catch (err) {
installError = err;
progress.setLabel("Gateway daemon install failed.");
}
},
);
if (installError) {
note(`Gateway daemon install failed: ${String(installError)}`, "Gateway");
if (process.platform === "win32") {
note(
"Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip daemon install.",
"Gateway",
);
} else {
note("Tip: rerun `clawdbot daemon install` after fixing the error.", "Gateway");
}
return;
}
shouldCheckLinger = true;
}

View File

@@ -69,12 +69,24 @@ export async function installGatewayDaemonNonInteractive(params: {
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
: undefined,
});
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment,
});
try {
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment,
});
} catch (err) {
runtime.error(`Gateway daemon install failed: ${String(err)}`);
if (process.platform === "win32") {
runtime.log(
"Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip daemon install.",
);
} else {
runtime.log("Tip: rerun `clawdbot daemon install` after fixing the error.");
}
return;
}
await ensureSystemdUserLingerNonInteractive({ runtime });
}

View File

@@ -26,6 +26,15 @@ function quoteCmdArg(value: string): string {
return `"${value.replace(/"/g, '\\"')}"`;
}
function resolveTaskUser(env: Record<string, string | undefined>): string | null {
const username = env.USERNAME || env.USER || env.LOGNAME;
if (!username) return null;
if (username.includes("\\")) return username;
const domain = env.USERDOMAIN;
if (domain) return `${domain}\\${username}`;
return username;
}
function parseCommandLine(value: string): string[] {
const args: string[] = [];
let current = "";
@@ -216,7 +225,7 @@ export async function installScheduledTask({
const taskName = resolveGatewayWindowsTaskName(env.CLAWDBOT_PROFILE);
const quotedScript = quoteCmdArg(scriptPath);
const create = await execSchtasks([
const baseArgs = [
"/Create",
"/F",
"/SC",
@@ -227,9 +236,20 @@ export async function installScheduledTask({
taskName,
"/TR",
quotedScript,
]);
];
const taskUser = resolveTaskUser(env);
let create = await execSchtasks(
taskUser ? [...baseArgs, "/RU", taskUser, "/NP", "/IT"] : baseArgs,
);
if (create.code !== 0 && taskUser) {
create = await execSchtasks(baseArgs);
}
if (create.code !== 0) {
throw new Error(`schtasks create failed: ${create.stderr || create.stdout}`.trim());
const detail = create.stderr || create.stdout;
const hint = /access is denied/i.test(detail)
? " Run PowerShell as Administrator or rerun without installing the daemon."
: "";
throw new Error(`schtasks create failed: ${detail}${hint}`.trim());
}
await execSchtasks(["/Run", "/TN", taskName]);