#!/usr/bin/env node import { basename, dirname, join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { commandToString, ensureParentDir, flag, option, parseArgs, readEnvFile, runCommand, timestampSlug, } from './ops-utils.mjs'; function usage() { return `Usage: node scripts/backup-server-data.mjs [options] Backs up the server_data Docker volume that stores runtime AI provider config. Options: --env-file Compose env file used to derive COMPOSE_PROJECT_NAME (default: .env.production) --volume Explicit Docker volume name --backup-dir Directory for generated backups (default: backups/server-data) --output Exact output .tgz path --image Utility image (default: alpine:3.20) --dry-run Print the docker run command without writing a file --help Show this help `; } function buildOptions(argv) { const args = parseArgs(argv); if (flag(args, 'help')) return { help: true }; const envFile = option(args, 'env-file', '.env.production'); const env = readEnvFile(envFile, { optional: flag(args, 'dry-run') }); const projectName = env.COMPOSE_PROJECT_NAME || 'ftb_pm'; const volume = option(args, 'volume', `${projectName}_server_data`); const backupDir = option(args, 'backup-dir', 'backups/server-data'); const output = option( args, 'output', join(backupDir, `${volume}-${timestampSlug()}.tgz`), ); return { help: false, dryRun: flag(args, 'dry-run'), image: option(args, 'image', 'alpine:3.20'), volume, output: resolve(output), }; } export function buildServerDataBackupCommand(options) { const outputDir = dirname(options.output); const outputName = basename(options.output); return [ 'docker', 'run', '--rm', '-v', `${options.volume}:/data:ro`, '-v', `${outputDir}:/backup`, options.image, 'sh', '-lc', `tar -czf /backup/${outputName} -C /data .`, ]; } export async function main(argv = process.argv.slice(2)) { const options = buildOptions(argv); if (options.help) { process.stdout.write(usage()); return; } const command = buildServerDataBackupCommand(options); if (options.dryRun) { process.stdout.write(`[dry-run] server_data backup would write: ${options.output}\n`); process.stdout.write(`${commandToString(command)}\n`); return; } ensureParentDir(options.output); await runCommand(command); process.stdout.write(`server_data backup written: ${options.output}\n`); } if (import.meta.url === pathToFileURL(process.argv[1]).href) { main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exit(1); }); }