added to remote repo

This commit is contained in:
Philipp
2026-05-21 08:18:29 +02:00
parent 8697c9f405
commit cc599b6e18
8049 changed files with 1096323 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
import { spawn } from 'node:child_process';
import { PassThrough } from 'node:stream';
import { resticProcessEnv } from './config.js';
export class CommandError extends Error {
constructor(message, result) {
super(message);
this.name = 'CommandError';
this.result = result;
}
}
export function spawnCommand(command, args = [], options = {}) {
const {
env = process.env,
input = null,
log = null,
ignoreExitCode = false,
cwd = process.cwd(),
} = options;
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd,
env,
shell: false,
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => {
const text = chunk.toString();
stdout += text;
log?.(text.trimEnd());
});
child.stderr.on('data', (chunk) => {
const text = chunk.toString();
stderr += text;
log?.(text.trimEnd());
});
child.on('error', (error) => {
reject(error);
});
child.on('close', (exitCode) => {
const result = { stdout, stderr, exitCode };
if (exitCode !== 0 && !ignoreExitCode) {
reject(new CommandError(stderr.trim() || `${command} exited with ${exitCode}`, result));
return;
}
resolve(result);
});
if (input) {
input.on('error', (error) => {
child.kill('SIGTERM');
reject(error);
});
input.pipe(child.stdin);
} else {
child.stdin.end();
}
});
}
export function runRestic(args, options = {}) {
return spawnCommand('restic', args, {
...options,
env: resticProcessEnv(),
});
}
export function streamResticDumpToDd(snapshotId, filename, outputPath, log) {
return new Promise((resolve, reject) => {
const restic = spawn('restic', ['dump', snapshotId, filename], {
env: resticProcessEnv(),
shell: false,
stdio: ['ignore', 'pipe', 'pipe'],
});
const dd = spawn('dd', [`of=${outputPath}`, 'bs=4M', 'conv=sparse', 'status=none'], {
env: process.env,
shell: false,
stdio: ['pipe', 'pipe', 'pipe'],
});
const errors = [];
let resticClosed = false;
let ddClosed = false;
const pipe = new PassThrough();
restic.stdout.pipe(pipe).pipe(dd.stdin);
restic.stderr.on('data', (chunk) => log?.(chunk.toString().trimEnd()));
dd.stderr.on('data', (chunk) => log?.(chunk.toString().trimEnd()));
restic.on('error', reject);
dd.on('error', reject);
restic.on('close', (code) => {
resticClosed = true;
if (code !== 0) {
errors.push(`restic dump exited with ${code}`);
dd.stdin.destroy();
}
maybeFinish();
});
dd.on('close', (code) => {
ddClosed = true;
if (code !== 0) {
errors.push(`dd exited with ${code}`);
}
maybeFinish();
});
function maybeFinish() {
if (!resticClosed || !ddClosed) return;
if (errors.length) {
reject(new Error(errors.join('; ')));
return;
}
resolve();
}
});
}