210 lines
6.3 KiB
TypeScript
210 lines
6.3 KiB
TypeScript
import { CheckExecutor } from './check-executor.ts';
|
|
import { DEFAULT_CONFIG_PATH, loadConfig, validateConfig, writeConfig } from './config.ts';
|
|
import type { IUptimeRunnerConfig, TCheckJob } from './interfaces.ts';
|
|
import { UptimeRunner } from './runner.ts';
|
|
import { UptimeRunnerSystemd } from './systemd.ts';
|
|
import denoConfig from '../deno.json' with { type: 'json' };
|
|
|
|
export class UptimeRunnerCli {
|
|
public async parseAndExecute(argsArg: string[]): Promise<void> {
|
|
const command = argsArg[0] ?? 'help';
|
|
const commandArgs = argsArg.slice(1);
|
|
|
|
switch (command) {
|
|
case 'run':
|
|
await this.run(commandArgs, false);
|
|
break;
|
|
case 'once':
|
|
await this.run(commandArgs, true);
|
|
break;
|
|
case 'check':
|
|
await this.check(commandArgs);
|
|
break;
|
|
case 'config':
|
|
await this.config(commandArgs);
|
|
break;
|
|
case 'service':
|
|
await this.service(commandArgs);
|
|
break;
|
|
case '--version':
|
|
case '-v':
|
|
case 'version':
|
|
console.log(denoConfig.version);
|
|
break;
|
|
case 'help':
|
|
case '--help':
|
|
case '-h':
|
|
this.showHelp();
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown command: ${command}`);
|
|
}
|
|
}
|
|
|
|
private async run(argsArg: string[], onceArg: boolean): Promise<void> {
|
|
const flags = parseFlags(argsArg);
|
|
const config = await this.loadConfigFromFlags(flags);
|
|
const runner = new UptimeRunner(config);
|
|
|
|
if (onceArg) {
|
|
const result = await runner.runOnce();
|
|
console.log(JSON.stringify(result, null, 2));
|
|
return;
|
|
}
|
|
|
|
await runner.run();
|
|
}
|
|
|
|
private async check(argsArg: string[]): Promise<void> {
|
|
const url = argsArg.find((arg) => !arg.startsWith('--'));
|
|
if (!url) {
|
|
throw new Error('Usage: uptimerunner check <url>');
|
|
}
|
|
|
|
const check: TCheckJob = {
|
|
id: `manual-${Date.now().toString(36)}`,
|
|
type: 'http',
|
|
url,
|
|
expectedStatusCodes: [200],
|
|
};
|
|
const executor = new CheckExecutor('manual');
|
|
const result = await executor.execute(check);
|
|
console.log(JSON.stringify(result, null, 2));
|
|
if (result.status !== 'ok') {
|
|
Deno.exit(2);
|
|
}
|
|
}
|
|
|
|
private async config(argsArg: string[]): Promise<void> {
|
|
const subcommand = argsArg[0] ?? 'show';
|
|
const flags = parseFlags(argsArg.slice(1));
|
|
const configPath = flags.config ?? DEFAULT_CONFIG_PATH;
|
|
|
|
switch (subcommand) {
|
|
case 'show': {
|
|
const config = await loadConfig(configPath);
|
|
console.log(JSON.stringify({ ...config, token: mask(config.token) }, null, 2));
|
|
break;
|
|
}
|
|
case 'write': {
|
|
const config: IUptimeRunnerConfig = {
|
|
instanceUrl: requiredFlag(flags, 'url'),
|
|
runnerId: requiredFlag(flags, 'runner-id'),
|
|
token: requiredFlag(flags, 'token'),
|
|
pollIntervalMs: flags.interval ? Number(flags.interval) : 30000,
|
|
labels: flags.labels?.split(',').map((labelArg) => labelArg.trim()).filter(Boolean),
|
|
maxConcurrentChecks: flags.concurrency ? Number(flags.concurrency) : 8,
|
|
};
|
|
await writeConfig(config, configPath);
|
|
console.log(`Config written to ${configPath}`);
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error(`Unknown config command: ${subcommand}`);
|
|
}
|
|
}
|
|
|
|
private async service(argsArg: string[]): Promise<void> {
|
|
const subcommand = argsArg[0] ?? 'status';
|
|
const systemd = new UptimeRunnerSystemd();
|
|
|
|
switch (subcommand) {
|
|
case 'install':
|
|
await systemd.install();
|
|
break;
|
|
case 'uninstall':
|
|
await systemd.uninstall();
|
|
break;
|
|
case 'start':
|
|
await systemd.start();
|
|
break;
|
|
case 'stop':
|
|
await systemd.stop();
|
|
break;
|
|
case 'restart':
|
|
await systemd.restart();
|
|
break;
|
|
case 'status':
|
|
await systemd.status();
|
|
break;
|
|
case 'logs':
|
|
await systemd.logs();
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown service command: ${subcommand}`);
|
|
}
|
|
}
|
|
|
|
private async loadConfigFromFlags(
|
|
flagsArg: Record<string, string>,
|
|
): Promise<IUptimeRunnerConfig> {
|
|
const configPath = flagsArg.config ?? DEFAULT_CONFIG_PATH;
|
|
const fileConfig = await loadConfig(configPath).catch((error) => {
|
|
if (flagsArg.url && flagsArg.token && flagsArg['runner-id']) {
|
|
return {} as Partial<IUptimeRunnerConfig>;
|
|
}
|
|
throw error;
|
|
});
|
|
|
|
const config = {
|
|
...fileConfig,
|
|
instanceUrl: flagsArg.url ?? fileConfig.instanceUrl,
|
|
runnerId: flagsArg['runner-id'] ?? fileConfig.runnerId,
|
|
token: flagsArg.token ?? fileConfig.token,
|
|
pollIntervalMs: flagsArg.interval ? Number(flagsArg.interval) : fileConfig.pollIntervalMs,
|
|
labels: flagsArg.labels
|
|
? flagsArg.labels.split(',').map((labelArg) => labelArg.trim()).filter(Boolean)
|
|
: fileConfig.labels,
|
|
maxConcurrentChecks: flagsArg.concurrency
|
|
? Number(flagsArg.concurrency)
|
|
: fileConfig.maxConcurrentChecks,
|
|
};
|
|
validateConfig(config);
|
|
return config;
|
|
}
|
|
|
|
private showHelp(): void {
|
|
console.log(`uptimerunner ${denoConfig.version}
|
|
|
|
Usage:
|
|
uptimerunner run [--config path] [--url https://uptime.link] [--token token] [--runner-id id]
|
|
uptimerunner once [--config path]
|
|
uptimerunner check <url>
|
|
uptimerunner config write --url https://uptime.link --runner-id edge-1 --token token
|
|
uptimerunner service install|start|stop|restart|status|logs|uninstall
|
|
|
|
Environment:
|
|
UPTIMERUNNER_CONFIG
|
|
UPTIMERUNNER_INSTANCE_URL
|
|
UPTIMERUNNER_RUNNER_ID
|
|
UPTIMERUNNER_TOKEN
|
|
UPTIMERUNNER_LABELS
|
|
`);
|
|
}
|
|
}
|
|
|
|
function parseFlags(argsArg: string[]): Record<string, string> {
|
|
const flags: Record<string, string> = {};
|
|
for (let index = 0; index < argsArg.length; index++) {
|
|
const arg = argsArg[index];
|
|
if (!arg.startsWith('--')) {
|
|
continue;
|
|
}
|
|
const [rawName, inlineValue] = arg.slice(2).split('=', 2);
|
|
flags[rawName] = inlineValue ?? argsArg[++index];
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
function requiredFlag(flagsArg: Record<string, string>, nameArg: string): string {
|
|
const value = flagsArg[nameArg];
|
|
if (!value) {
|
|
throw new Error(`Missing required --${nameArg} flag.`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function mask(valueArg: string): string {
|
|
return valueArg.length <= 8 ? '********' : `${valueArg.slice(0, 4)}...${valueArg.slice(-4)}`;
|
|
}
|