583 lines
No EOL
20 KiB
JavaScript
Executable file
583 lines
No EOL
20 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
import { promisify, parseArgs } from 'util';
|
|
import { homedir, hostname, platform } from 'os';
|
|
import { exec, execSync } from 'child_process';
|
|
import { mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
import { dirname, join } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { readFile, writeFile, access } from 'fs/promises';
|
|
|
|
async function initRepository(repoUrl, password) {
|
|
try {
|
|
console.log(`[restic-setup-client] Initializing repository: ${repoUrl}`);
|
|
try {
|
|
execSync('RESTIC_PASSWORD="' + password + '" restic -r "' + repoUrl + '" snapshots', {
|
|
encoding: "utf8",
|
|
stdio: "pipe"
|
|
});
|
|
console.log(`[restic-setup-client] Repository already exists: ${repoUrl}`);
|
|
return {
|
|
success: true,
|
|
repoUrl
|
|
};
|
|
} catch {
|
|
execSync('RESTIC_PASSWORD="' + password + '" restic -r "' + repoUrl + '" init', {
|
|
encoding: "utf8",
|
|
stdio: "inherit"
|
|
});
|
|
console.log(`[restic-setup-client] \u2705 Repository initialized: ${repoUrl}`);
|
|
return {
|
|
success: true,
|
|
repoUrl
|
|
};
|
|
}
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
console.error(`[restic-setup-client] \u274C Repository initialization failed: ${errorMessage}`);
|
|
return {
|
|
success: false,
|
|
repoUrl,
|
|
error: errorMessage
|
|
};
|
|
}
|
|
}
|
|
|
|
// src/lib/client.ts
|
|
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
var TEMPLATES_DIR = join(__dirname$1, "../templates");
|
|
function deployTimers() {
|
|
const systemdDir = join(homedir(), ".config/systemd/user");
|
|
mkdirSync(systemdDir, { recursive: true });
|
|
const files = [
|
|
"restic-backup-code.service",
|
|
"restic-backup-code.timer",
|
|
"restic-backup-dotfiles.service",
|
|
"restic-backup-dotfiles.timer"
|
|
];
|
|
for (const file of files) {
|
|
const templatePath = join(TEMPLATES_DIR, file);
|
|
const template = readFileSync(templatePath, "utf8");
|
|
const targetPath = join(systemdDir, file);
|
|
writeFileSync(targetPath, template, { mode: 420 });
|
|
console.log(`[restic-setup-client] Deployed: ${targetPath}`);
|
|
}
|
|
execSync("systemctl --user daemon-reload", { encoding: "utf8", stdio: "inherit" });
|
|
execSync("systemctl --user enable --now restic-backup-code.timer", {
|
|
encoding: "utf8",
|
|
stdio: "inherit"
|
|
});
|
|
execSync("systemctl --user enable --now restic-backup-dotfiles.timer", {
|
|
encoding: "utf8",
|
|
stdio: "inherit"
|
|
});
|
|
console.log("[restic-setup-client] \u2705 Timers enabled and started");
|
|
}
|
|
async function setupClient(config) {
|
|
const {
|
|
serverUrl,
|
|
hostname: hostname2 = hostname(),
|
|
password,
|
|
configDir = join(homedir(), ".config/restic")
|
|
} = config;
|
|
try {
|
|
console.log(`[restic-setup-client] Setting up backup client for ${hostname2}...`);
|
|
if (!serverUrl || !password) {
|
|
throw new Error("serverUrl and password are required");
|
|
}
|
|
mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
const passwordFile = join(configDir, "password");
|
|
writeFileSync(passwordFile, password, { mode: 384 });
|
|
console.log(`[restic-setup-client] Password file created: ${passwordFile}`);
|
|
const dotfilesIncludeTemplate = join(TEMPLATES_DIR, "restic-dotfiles-include.txt");
|
|
const dotfilesIncludePath = join(configDir, "dotfiles-include.txt");
|
|
const dotfilesIncludeContent = readFileSync(dotfilesIncludeTemplate, "utf8");
|
|
writeFileSync(dotfilesIncludePath, dotfilesIncludeContent, { mode: 420 });
|
|
console.log(`[restic-setup-client] Dotfiles include list created: ${dotfilesIncludePath}`);
|
|
const codeRepoUrl = "rest:" + serverUrl + "/" + hostname2 + "-code";
|
|
const dotfilesRepoUrl = "rest:" + serverUrl + "/" + hostname2 + "-dotfiles";
|
|
console.log("[restic-setup-client] Initializing repositories...");
|
|
const codeResult = await initRepository(codeRepoUrl, password);
|
|
if (!codeResult.success) {
|
|
throw new Error("Failed to initialize code repository: " + codeResult.error);
|
|
}
|
|
const dotfilesResult = await initRepository(dotfilesRepoUrl, password);
|
|
if (!dotfilesResult.success) {
|
|
throw new Error("Failed to initialize dotfiles repository: " + dotfilesResult.error);
|
|
}
|
|
console.log("[restic-setup-client] Deploying systemd timers...");
|
|
deployTimers();
|
|
console.log("[restic-setup-client] \u2705 Client setup complete!");
|
|
return {
|
|
success: true,
|
|
codeRepoUrl,
|
|
dotfilesRepoUrl
|
|
};
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
console.error(`[restic-setup-client] \u274C Setup failed: ${errorMessage}`);
|
|
return {
|
|
success: false,
|
|
codeRepoUrl: "rest:" + serverUrl + "/" + hostname2 + "-code",
|
|
dotfilesRepoUrl: "rest:" + serverUrl + "/" + hostname2 + "-dotfiles",
|
|
error: errorMessage
|
|
};
|
|
}
|
|
}
|
|
var execAsync = promisify(exec);
|
|
var VAULT_DIR = join(homedir(), ".vault");
|
|
var METADATA_FILE = join(VAULT_DIR, "restic-password.meta.json");
|
|
var PASSWORD_FILE = join(VAULT_DIR, "restic-password.txt");
|
|
var ROTATION_THRESHOLD_DAYS = 365;
|
|
var CODE_REPO_NAME = "code";
|
|
var DOTFILES_REPO_NAME = "dotfiles";
|
|
async function loadPasswordMetadata() {
|
|
try {
|
|
const data = await readFile(METADATA_FILE, "utf-8");
|
|
return JSON.parse(data);
|
|
} catch (err) {
|
|
if (err.code === "ENOENT") {
|
|
return null;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
async function savePasswordMetadata(metadata) {
|
|
await writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2), "utf-8");
|
|
await execAsync(`chmod 600 "${METADATA_FILE}"`);
|
|
}
|
|
async function initializePasswordMetadata(hostname2) {
|
|
const metadata = {
|
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
rotationCount: 0,
|
|
hostname: hostname2
|
|
};
|
|
await savePasswordMetadata(metadata);
|
|
return metadata;
|
|
}
|
|
async function getPasswordAge() {
|
|
const metadata = await loadPasswordMetadata();
|
|
if (!metadata) {
|
|
throw new Error("Password metadata not found. Run restic-setup-client setup first.");
|
|
}
|
|
const createdAt = new Date(metadata.createdAt);
|
|
const lastRotatedAt = metadata.rotatedAt ? new Date(metadata.rotatedAt) : void 0;
|
|
const referenceDate = lastRotatedAt || createdAt;
|
|
const now = /* @__PURE__ */ new Date();
|
|
const ageInMs = now.getTime() - referenceDate.getTime();
|
|
const ageInDays = Math.floor(ageInMs / (1e3 * 60 * 60 * 24));
|
|
const daysUntilRecommendedRotation = ROTATION_THRESHOLD_DAYS - ageInDays;
|
|
const shouldRotate = ageInDays >= ROTATION_THRESHOLD_DAYS;
|
|
return {
|
|
ageInDays,
|
|
createdAt,
|
|
lastRotatedAt,
|
|
rotationCount: metadata.rotationCount,
|
|
shouldRotate,
|
|
daysUntilRecommendedRotation
|
|
};
|
|
}
|
|
async function shouldPromptRotation() {
|
|
try {
|
|
const ageInfo = await getPasswordAge();
|
|
return ageInfo.shouldRotate;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
function generatePassword() {
|
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
const length = 32;
|
|
const array = new Uint8Array(length);
|
|
crypto.getRandomValues(array);
|
|
return Array.from(array).map((byte) => chars[byte % chars.length]).join("");
|
|
}
|
|
async function hashPassword(password) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(password);
|
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
async function rotatePassword(oldPassword, newPassword, updateKeychain = true) {
|
|
const result = {
|
|
success: false,
|
|
oldPasswordHash: "",
|
|
newPasswordHash: "",
|
|
repositoriesUpdated: [],
|
|
errors: []
|
|
};
|
|
try {
|
|
if (!oldPassword) {
|
|
oldPassword = (await readFile(PASSWORD_FILE, "utf-8")).trim();
|
|
}
|
|
result.oldPasswordHash = await hashPassword(oldPassword);
|
|
if (!newPassword) {
|
|
newPassword = generatePassword();
|
|
}
|
|
result.newPasswordHash = await hashPassword(newPassword);
|
|
const repos = await getResticRepositories();
|
|
for (const repo of repos) {
|
|
try {
|
|
await changeRepositoryPassword(repo, oldPassword, newPassword);
|
|
result.repositoriesUpdated.push(repo.name);
|
|
} catch (err) {
|
|
const error = `Failed to update ${repo.name}: ${err.message}`;
|
|
result.errors.push(error);
|
|
}
|
|
}
|
|
if (result.repositoriesUpdated.length > 0) {
|
|
await writeFile(PASSWORD_FILE, newPassword + "\n", "utf-8");
|
|
await execAsync(`chmod 600 "${PASSWORD_FILE}"`);
|
|
const metadata = await loadPasswordMetadata();
|
|
if (metadata) {
|
|
metadata.rotatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
metadata.rotationCount += 1;
|
|
await savePasswordMetadata(metadata);
|
|
}
|
|
result.success = result.errors.length === 0;
|
|
if (updateKeychain && platform() === "darwin" && result.success) {
|
|
try {
|
|
await updateKeychainPassword(newPassword);
|
|
result.keychainUpdated = true;
|
|
} catch (err) {
|
|
result.errors.push(`Keychain update failed: ${err.message}`);
|
|
result.keychainUpdated = false;
|
|
}
|
|
}
|
|
} else {
|
|
result.success = false;
|
|
result.errors.push("No repositories were updated successfully");
|
|
}
|
|
} catch (err) {
|
|
result.success = false;
|
|
result.errors.push(`Rotation failed: ${err.message}`);
|
|
}
|
|
return result;
|
|
}
|
|
async function updateKeychainPassword(newPassword) {
|
|
try {
|
|
const { storeInKeychain } = await import('@lilith/vault-setup-client');
|
|
const result = await storeInKeychain({
|
|
service: "restic-backup",
|
|
account: "lilith-platform-workstations",
|
|
password: newPassword
|
|
});
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Unknown Keychain error");
|
|
}
|
|
} catch (err) {
|
|
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
throw new Error("Keychain integration requires @lilith/vault-setup-client package");
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
async function getResticRepositories() {
|
|
const repos = [];
|
|
const repoNames = [CODE_REPO_NAME, DOTFILES_REPO_NAME];
|
|
for (const name of repoNames) {
|
|
const servicePath = `/etc/systemd/system/restic-backup-${name}.service`;
|
|
try {
|
|
await access(servicePath);
|
|
const serviceContent = await readFile(servicePath, "utf-8");
|
|
const repoMatch = serviceContent.match(/Environment="RESTIC_REPOSITORY=(.+?)"/);
|
|
if (repoMatch) {
|
|
repos.push({
|
|
name,
|
|
url: repoMatch[1],
|
|
passwordFile: PASSWORD_FILE
|
|
});
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
return repos;
|
|
}
|
|
async function changeRepositoryPassword(repo, oldPassword, newPassword) {
|
|
const tmpOldFile = `/tmp/restic-old-${Date.now()}.txt`;
|
|
const tmpNewFile = `/tmp/restic-new-${Date.now()}.txt`;
|
|
try {
|
|
await writeFile(tmpOldFile, oldPassword, "utf-8");
|
|
await writeFile(tmpNewFile, newPassword, "utf-8");
|
|
await execAsync(`chmod 600 "${tmpOldFile}" "${tmpNewFile}"`);
|
|
const cmd = `RESTIC_REPOSITORY="${repo.url}" RESTIC_PASSWORD_FILE="${tmpOldFile}" restic key passwd --new-password-file="${tmpNewFile}"`;
|
|
const { stderr } = await execAsync(cmd);
|
|
if (stderr && stderr.includes("error")) {
|
|
throw new Error(stderr);
|
|
}
|
|
} finally {
|
|
try {
|
|
await execAsync(`rm -f "${tmpOldFile}" "${tmpNewFile}"`);
|
|
} catch {
|
|
}
|
|
}
|
|
}
|
|
|
|
// src/cli.ts
|
|
async function setupCommand(args) {
|
|
const { values } = parseArgs({
|
|
args,
|
|
options: {
|
|
server: { type: "string", short: "s" },
|
|
hostname: { type: "string" },
|
|
password: { type: "string", short: "p" },
|
|
help: { type: "boolean", short: "h" }
|
|
}
|
|
});
|
|
if (values.help) {
|
|
console.log(`
|
|
Usage: restic-setup-client setup [options]
|
|
|
|
Setup restic backup client on this workstation.
|
|
|
|
Options:
|
|
-s, --server <url> Restic REST server URL (required)
|
|
--hostname <hostname> Workstation hostname (default: $(hostname))
|
|
-p, --password <password> Restic repository password (required)
|
|
-h, --help Show this help message
|
|
|
|
Example:
|
|
restic-setup-client setup \\
|
|
--server http://10.0.0.11:8000 \\
|
|
--password CWPVvKALTwyJfbdVE3oIq7L8Wc7MH4Pz
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
if (!values.server || !values.password) {
|
|
console.error("Error: --server and --password are required");
|
|
console.error('Run "restic-setup-client setup --help" for usage');
|
|
process.exit(1);
|
|
}
|
|
try {
|
|
const host = values.hostname || hostname();
|
|
const result = await setupClient({
|
|
serverUrl: values.server,
|
|
hostname: host,
|
|
password: values.password
|
|
});
|
|
if (result.success) {
|
|
await initializePasswordMetadata(host);
|
|
console.log("\n\u2705 Backup client setup successful!");
|
|
console.log(`
|
|
Repositories:`);
|
|
console.log(` Code: ${result.codeRepoUrl}`);
|
|
console.log(` Dotfiles: ${result.dotfilesRepoUrl}`);
|
|
console.log(`
|
|
Systemd timers have been enabled and started.`);
|
|
console.log(`Check status: systemctl --user list-timers | grep restic`);
|
|
console.log(`
|
|
\u{1F4A1} Password metadata initialized. Run 'restic-setup-client check-password' to view age.`);
|
|
process.exit(0);
|
|
} else {
|
|
console.error(`
|
|
\u274C Setup failed: ${result.error}`);
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
console.error("\n\u274C Error:", error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
async function checkPasswordCommand(args) {
|
|
const { values } = parseArgs({
|
|
args,
|
|
options: {
|
|
help: { type: "boolean", short: "h" },
|
|
json: { type: "boolean" }
|
|
}
|
|
});
|
|
if (values.help) {
|
|
console.log(`
|
|
Usage: restic-setup-client check-password [options]
|
|
|
|
Check the age of the restic repository password and get rotation recommendations.
|
|
|
|
Options:
|
|
--json Output as JSON
|
|
-h, --help Show this help message
|
|
|
|
Example:
|
|
restic-setup-client check-password
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
try {
|
|
const ageInfo = await getPasswordAge();
|
|
if (values.json) {
|
|
console.log(JSON.stringify(ageInfo, null, 2));
|
|
process.exit(0);
|
|
}
|
|
console.log("\n\u{1F4CA} Password Age Information");
|
|
console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
|
|
console.log(`Created: ${ageInfo.createdAt.toISOString().split("T")[0]}`);
|
|
if (ageInfo.lastRotatedAt) {
|
|
console.log(`Last Rotated: ${ageInfo.lastRotatedAt.toISOString().split("T")[0]}`);
|
|
}
|
|
console.log(`Age: ${ageInfo.ageInDays} days`);
|
|
console.log(`Rotations: ${ageInfo.rotationCount}`);
|
|
console.log(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`);
|
|
if (ageInfo.shouldRotate) {
|
|
console.log(`
|
|
\u26A0\uFE0F Password is ${ageInfo.ageInDays} days old (threshold: 365 days)`);
|
|
console.log(`
|
|
\u{1F504} Recommendation: Rotate your password now!`);
|
|
console.log(`
|
|
Run: restic-setup-client rotate-password`);
|
|
} else {
|
|
console.log(`
|
|
\u2705 Password is within recommended age`);
|
|
console.log(`
|
|
\u{1F4C5} Rotation recommended in: ${ageInfo.daysUntilRecommendedRotation} days`);
|
|
}
|
|
process.exit(ageInfo.shouldRotate ? 1 : 0);
|
|
} catch (error) {
|
|
console.error("\n\u274C Error:", error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
async function rotatePasswordCommand(args) {
|
|
const { values } = parseArgs({
|
|
args,
|
|
options: {
|
|
help: { type: "boolean", short: "h" },
|
|
force: { type: "boolean", short: "f" },
|
|
"new-password": { type: "string" },
|
|
"update-keychain": { type: "boolean" },
|
|
"no-keychain": { type: "boolean" }
|
|
}
|
|
});
|
|
if (values.help) {
|
|
console.log(`
|
|
Usage: restic-setup-client rotate-password [options]
|
|
|
|
Rotate the restic repository password. This updates all configured repositories.
|
|
|
|
Options:
|
|
-f, --force Skip age check confirmation
|
|
--new-password <pwd> Use specific password (default: auto-generate)
|
|
--update-keychain Update macOS Keychain (default: true on macOS)
|
|
--no-keychain Skip Keychain update
|
|
-h, --help Show this help message
|
|
|
|
Example:
|
|
restic-setup-client rotate-password
|
|
restic-setup-client rotate-password --force
|
|
restic-setup-client rotate-password --new-password "MyNewSecurePassword123"
|
|
restic-setup-client rotate-password --no-keychain
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
try {
|
|
if (!values.force) {
|
|
const ageInfo = await getPasswordAge();
|
|
console.log(`
|
|
\u{1F4CA} Current password age: ${ageInfo.ageInDays} days`);
|
|
console.log(` Rotations performed: ${ageInfo.rotationCount}`);
|
|
if (!ageInfo.shouldRotate) {
|
|
console.log(`
|
|
\u26A0\uFE0F Password is still within recommended age (${ageInfo.daysUntilRecommendedRotation} days until 365)`);
|
|
console.log(` Use --force to rotate anyway`);
|
|
process.exit(0);
|
|
}
|
|
}
|
|
const updateKeychain = values["no-keychain"] ? false : values["update-keychain"] ?? true;
|
|
console.log(`
|
|
\u{1F504} Starting password rotation...`);
|
|
console.log(` This will update all configured restic repositories`);
|
|
const result = await rotatePassword(
|
|
void 0,
|
|
values["new-password"],
|
|
updateKeychain
|
|
);
|
|
if (result.success) {
|
|
console.log(`
|
|
\u2705 Password rotation successful!`);
|
|
console.log(`
|
|
Repositories updated:`);
|
|
for (const repo of result.repositoriesUpdated) {
|
|
console.log(` \u2713 ${repo}`);
|
|
}
|
|
console.log(`
|
|
\u{1F4A1} New password saved to: ~/.vault/restic-password.txt`);
|
|
console.log(` Old password hash: ${result.oldPasswordHash.substring(0, 16)}...`);
|
|
console.log(` New password hash: ${result.newPasswordHash.substring(0, 16)}...`);
|
|
if (result.keychainUpdated === true) {
|
|
console.log(`
|
|
\u{1F510} macOS Keychain updated`);
|
|
console.log(` Service: restic-backup`);
|
|
console.log(` Account: lilith-platform-workstations`);
|
|
} else if (result.keychainUpdated === false) {
|
|
console.log(`
|
|
\u26A0\uFE0F Keychain update failed (see errors above)`);
|
|
}
|
|
} else {
|
|
console.error(`
|
|
\u274C Password rotation failed`);
|
|
console.error(`
|
|
Repositories updated: ${result.repositoriesUpdated.length}`);
|
|
if (result.errors.length > 0) {
|
|
console.error(`
|
|
Errors:`);
|
|
for (const error of result.errors) {
|
|
console.error(` \u2717 ${error}`);
|
|
}
|
|
}
|
|
process.exit(1);
|
|
}
|
|
} catch (error) {
|
|
console.error("\n\u274C Error:", error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
const command = args[0];
|
|
if (command !== "rotate-password" && await shouldPromptRotation()) {
|
|
console.log(`
|
|
\u26A0\uFE0F Your restic password is over 365 days old!`);
|
|
console.log(` Run: restic-setup-client check-password`);
|
|
console.log(` Then: restic-setup-client rotate-password
|
|
`);
|
|
}
|
|
if (!command || command === "--help" || command === "-h") {
|
|
console.log(`
|
|
restic-setup-client - Automated Restic Backup Client Setup
|
|
|
|
Usage: restic-setup-client <command> [options]
|
|
|
|
Commands:
|
|
setup Setup restic backup client on this workstation
|
|
check-password Check password age and rotation status
|
|
rotate-password Rotate the restic repository password
|
|
|
|
Options:
|
|
-h, --help Show this help message
|
|
|
|
Examples:
|
|
restic-setup-client setup --server http://10.0.0.11:8000 --password <pwd>
|
|
restic-setup-client check-password
|
|
restic-setup-client rotate-password
|
|
|
|
Run 'restic-setup-client <command> --help' for command-specific options.
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
switch (command) {
|
|
case "setup":
|
|
await setupCommand(args.slice(1));
|
|
break;
|
|
case "check-password":
|
|
await checkPasswordCommand(args.slice(1));
|
|
break;
|
|
case "rotate-password":
|
|
await rotatePasswordCommand(args.slice(1));
|
|
break;
|
|
default:
|
|
console.error(`Unknown command: ${command}`);
|
|
console.error(`Run 'restic-setup-client --help' for usage`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
main();
|
|
//# sourceMappingURL=cli.js.map
|
|
//# sourceMappingURL=cli.js.map
|