76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { defineCommand } from "citty";
|
|
import { apiRequest, type ApiRequestInit } from "../lib/api";
|
|
import type { MaintenanceStatus } from "../shared/api";
|
|
|
|
type Request = <T>(path: string, init?: ApiRequestInit) => Promise<T>;
|
|
type Confirmation = (host: string) => Promise<boolean>;
|
|
const confirmationPath = join(
|
|
process.env.XDG_RUNTIME_DIR ?? "/tmp",
|
|
"kuber",
|
|
"maintenance-confirmation",
|
|
);
|
|
const TTL_MS = 60_000;
|
|
|
|
async function confirmed(host: string): Promise<boolean> {
|
|
try {
|
|
const value = JSON.parse(await readFile(confirmationPath, "utf8")) as {
|
|
host?: string;
|
|
expiresAt?: number;
|
|
};
|
|
if (
|
|
value.host === host &&
|
|
typeof value.expiresAt === "number" &&
|
|
value.expiresAt > Date.now()
|
|
) {
|
|
await rm(confirmationPath, { force: true });
|
|
return true;
|
|
}
|
|
} catch {}
|
|
await mkdir(join(confirmationPath, ".."), { recursive: true, mode: 0o700 });
|
|
await writeFile(
|
|
confirmationPath,
|
|
JSON.stringify({ host, expiresAt: Date.now() + TTL_MS }),
|
|
{ mode: 0o600 },
|
|
);
|
|
return false;
|
|
}
|
|
|
|
export async function runMaintenance(
|
|
host: string,
|
|
request: Request = apiRequest,
|
|
confirm: Confirmation = confirmed,
|
|
): Promise<MaintenanceStatus> {
|
|
const status = await request<MaintenanceStatus>(
|
|
`/maintenance/${encodeURIComponent(host)}`,
|
|
);
|
|
if (!(await confirm(status.host))) {
|
|
console.log(
|
|
`${status.host} maintenance is currently ${status.enabled ? "\x1b[31mON\x1b[0m" : "\x1b[32mOFF\x1b[0m"}, run this command again to toggle.`,
|
|
);
|
|
return status;
|
|
}
|
|
const result = await request<MaintenanceStatus>(
|
|
`/maintenance/${encodeURIComponent(status.host)}`,
|
|
{ method: "POST", json: { enabled: !status.enabled } },
|
|
);
|
|
console.log(
|
|
`${result.host} maintenance is now ${result.enabled ? "\x1b[31mON\x1b[0m" : "\x1b[32mOFF\x1b[0m"}`,
|
|
);
|
|
return result;
|
|
}
|
|
|
|
export const maintenance = defineCommand({
|
|
meta: {
|
|
name: "maintenance",
|
|
description: "Toggle maintenance page on hostnames",
|
|
},
|
|
args: {
|
|
host: { type: "positional", required: true, description: "DNS hostname" },
|
|
},
|
|
async run({ args }) {
|
|
await runMaintenance(args.host);
|
|
},
|
|
});
|