feat: add maintenance toggle command
This commit is contained in:
@@ -18,6 +18,7 @@ import { stop } from "./stop";
|
||||
import { up } from "./up";
|
||||
import { users } from "./users";
|
||||
import { trust } from "./trust";
|
||||
import { maintenance } from "./maintenance";
|
||||
|
||||
export const main = defineCommand({
|
||||
meta: {
|
||||
@@ -39,6 +40,7 @@ export const main = defineCommand({
|
||||
export: exportCommand,
|
||||
exec,
|
||||
logs,
|
||||
maintenance,
|
||||
login,
|
||||
logout,
|
||||
operations,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
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 a global hostname maintenance override",
|
||||
},
|
||||
args: {
|
||||
host: { type: "positional", required: true, description: "DNS hostname" },
|
||||
},
|
||||
async run({ args }) {
|
||||
await runMaintenance(args.host);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user