include missing files
Signed-off-by: Travis Glenn Hansen <travisghansen@yahoo.com>
This commit is contained in:
parent
7911bc9200
commit
d7919e766d
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
PLATFORM_TYPE=${1}
|
||||
|
||||
if [[ "${PLATFORM_TYPE}" == "build" ]]; then
|
||||
PLATFORM=$BUILDPLATFORM
|
||||
else
|
||||
PLATFORM=$TARGETPLATFORM
|
||||
fi
|
||||
|
||||
if [[ "x${PLATFORM}" == "x" ]]; then
|
||||
PLATFORM="linux/amd64"
|
||||
fi
|
||||
|
||||
# these come from the --platform option of buildx, indirectly from DOCKER_BUILD_PLATFORM in main.yaml
|
||||
if [ "$PLATFORM" = "linux/amd64" ]; then
|
||||
export PLATFORM_ARCH="amd64"
|
||||
elif [ "$PLATFORM" = "linux/arm64" ]; then
|
||||
export PLATFORM_ARCH="arm64"
|
||||
elif [ "$PLATFORM" = "linux/arm/v7" ]; then
|
||||
export PLATFORM_ARCH="armhv"
|
||||
else
|
||||
echo "unsupported/unknown kopia PLATFORM ${PLATFORM}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export DEB_FILE="kopia.deb"
|
||||
|
||||
echo "I am installing kopia $KOPIA_VERSION"
|
||||
|
||||
wget -O "${DEB_FILE}" "https://github.com/kopia/kopia/releases/download/v${KOPIA_VERSION}/kopia_${KOPIA_VERSION}_linux_${PLATFORM_ARCH}.deb"
|
||||
dpkg -i "${DEB_FILE}"
|
||||
|
||||
rm "${DEB_FILE}"
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
const _ = require("lodash");
|
||||
const cp = require("child_process");
|
||||
const uuidv4 = require("uuid").v4;
|
||||
|
||||
const DEFAULT_TIMEOUT = process.env.KOPIA_DEFAULT_TIMEOUT || 90000;
|
||||
|
||||
/**
|
||||
* https://kopia.io/
|
||||
*/
|
||||
class Kopia {
|
||||
constructor(options = {}) {
|
||||
const kopia = this;
|
||||
kopia.options = options;
|
||||
kopia.client_intance_uuid = uuidv4();
|
||||
|
||||
options.paths = options.paths || {};
|
||||
if (!options.paths.kopia) {
|
||||
options.paths.kopia = "kopia";
|
||||
}
|
||||
|
||||
if (!options.paths.sudo) {
|
||||
options.paths.sudo = "/usr/bin/sudo";
|
||||
}
|
||||
|
||||
if (!options.paths.chroot) {
|
||||
options.paths.chroot = "/usr/sbin/chroot";
|
||||
}
|
||||
|
||||
if (!options.env) {
|
||||
options.env = {};
|
||||
}
|
||||
|
||||
options.env[
|
||||
"KOPIA_CONFIG_PATH"
|
||||
] = `/tmp/kopia/${kopia.client_intance_uuid}/repository.config`;
|
||||
options.env["KOPIA_CHECK_FOR_UPDATES"] = "false";
|
||||
options.env[
|
||||
"KOPIA_CACHE_DIRECTORY"
|
||||
] = `/tmp/kopia/${kopia.client_intance_uuid}/cache`;
|
||||
options.env[
|
||||
"KOPIA_LOG_DIR"
|
||||
] = `/tmp/kopia/${kopia.client_intance_uuid}/log`;
|
||||
|
||||
if (!options.executor) {
|
||||
options.executor = {
|
||||
spawn: cp.spawn,
|
||||
};
|
||||
}
|
||||
|
||||
if (!options.logger) {
|
||||
options.logger = console;
|
||||
}
|
||||
|
||||
options.logger.info(
|
||||
`kopia client instantiated with client_instance_uuid: ${kopia.client_intance_uuid}`
|
||||
);
|
||||
|
||||
if (!options.global_flags) {
|
||||
options.global_flags = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia repository connect
|
||||
*
|
||||
* https://kopia.io/docs/reference/command-line/common/repository-connect-from-config/
|
||||
*
|
||||
* --override-hostname
|
||||
* --override-username
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async repositoryConnect(options = []) {
|
||||
const kopia = this;
|
||||
let args = ["repository", "connect"];
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
try {
|
||||
await kopia.exec(kopia.options.paths.kopia, args);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia repository status
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async repositoryStatus(options = []) {
|
||||
const kopia = this;
|
||||
let args = ["repository", "status", "--json"];
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args);
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia snapshot list
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshotList(options = []) {
|
||||
const kopia = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshot", "list", "--json"]);
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args, {
|
||||
operation: "snapshot-list",
|
||||
});
|
||||
|
||||
return result.parsed;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia snapshot list
|
||||
*
|
||||
* @param {*} snapshot_id
|
||||
*/
|
||||
async snapshotGet(snapshot_id) {
|
||||
const kopia = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshot", "list", "--json", "--all"]);
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args, {
|
||||
operation: "snapshot-list",
|
||||
});
|
||||
|
||||
return result.parsed.find((item) => {
|
||||
return item.id == snapshot_id;
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia snapshot create
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshotCreate(options = []) {
|
||||
const kopia = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshot", "create", "--json"]);
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args, {
|
||||
operation: "snapshot-create",
|
||||
});
|
||||
|
||||
return result.parsed;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia snapshot delete <id>
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshotDelete(options = []) {
|
||||
const kopia = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshot", "delete", "--delete"]);
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args, {
|
||||
operation: "snapshot-delete",
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (
|
||||
err.code == 1 &&
|
||||
(err.stderr.includes("no snapshots matched") ||
|
||||
err.stderr.includes("invalid content hash"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* kopia snapshot restore <snapshot_id[/sub/path]> /path/to/restore/to
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshotRestore(options = []) {
|
||||
const kopia = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshot", "restore"]);
|
||||
args = args.concat(kopia.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await kopia.exec(kopia.options.paths.kopia, args, {
|
||||
operation: "snapshot-restore",
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (
|
||||
err.code == 1 &&
|
||||
(err.stderr.includes("no snapshots matched") ||
|
||||
err.stderr.includes("invalid content hash"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
exec(command, args, options = {}) {
|
||||
if (!options.hasOwnProperty("timeout")) {
|
||||
options.timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
const kopia = this;
|
||||
args = args || [];
|
||||
|
||||
if (kopia.options.sudo) {
|
||||
args.unshift(command);
|
||||
command = kopia.options.paths.sudo;
|
||||
}
|
||||
|
||||
options.env = {
|
||||
...{},
|
||||
...process.env,
|
||||
...kopia.options.env,
|
||||
...options.env,
|
||||
};
|
||||
|
||||
let tokenIndex = args.findIndex((value) => {
|
||||
return value.trim() == "--token";
|
||||
});
|
||||
let cleansedArgs = [...args];
|
||||
if (tokenIndex >= 0) {
|
||||
cleansedArgs[tokenIndex + 1] = "redacted";
|
||||
}
|
||||
|
||||
const cleansedLog = `${command} ${cleansedArgs.join(" ")}`;
|
||||
console.log("executing kopia command: %s", cleansedLog);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let stdin;
|
||||
if (options.stdin) {
|
||||
stdin = options.stdin;
|
||||
delete options.stdin;
|
||||
}
|
||||
const child = kopia.options.executor.spawn(command, args, options);
|
||||
if (stdin) {
|
||||
child.stdin.write(stdin);
|
||||
}
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
const log_progress_output = _.debounce(
|
||||
(data) => {
|
||||
const lines = data.split("\n");
|
||||
/**
|
||||
* get last line, remove spinner, etc
|
||||
*/
|
||||
const line = lines
|
||||
.slice(-1)[0]
|
||||
.trim()
|
||||
.replace(/^[\/\\\-\|] /gi, "");
|
||||
kopia.options.logger.info(
|
||||
`kopia ${options.operation} progress: ${line.trim()}`
|
||||
);
|
||||
},
|
||||
250,
|
||||
{ leading: true, trailing: true, maxWait: 5000 }
|
||||
);
|
||||
|
||||
child.stdout.on("data", function (data) {
|
||||
data = String(data);
|
||||
stdout += data;
|
||||
});
|
||||
|
||||
child.stderr.on("data", function (data) {
|
||||
data = String(data);
|
||||
stderr += data;
|
||||
switch (options.operation) {
|
||||
case "snapshot-create":
|
||||
log_progress_output(data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
child.on("close", function (code) {
|
||||
const result = { code, stdout, stderr, timeout: false };
|
||||
|
||||
if (!result.parsed) {
|
||||
try {
|
||||
result.parsed = JSON.parse(result.stdout);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// timeout scenario
|
||||
if (code === null) {
|
||||
result.timeout = true;
|
||||
reject(result);
|
||||
}
|
||||
|
||||
if (code) {
|
||||
reject(result);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Kopia = Kopia;
|
||||
|
|
@ -0,0 +1,494 @@
|
|||
const _ = require("lodash");
|
||||
const cp = require("child_process");
|
||||
|
||||
const DEFAULT_TIMEOUT = process.env.RESTIC_DEFAULT_TIMEOUT || 90000;
|
||||
|
||||
/**
|
||||
* https://restic.net/
|
||||
*/
|
||||
class Restic {
|
||||
constructor(options = {}) {
|
||||
const restic = this;
|
||||
restic.options = options;
|
||||
|
||||
options.paths = options.paths || {};
|
||||
if (!options.paths.restic) {
|
||||
options.paths.restic = "restic";
|
||||
}
|
||||
|
||||
if (!options.paths.sudo) {
|
||||
options.paths.sudo = "/usr/bin/sudo";
|
||||
}
|
||||
|
||||
if (!options.paths.chroot) {
|
||||
options.paths.chroot = "/usr/sbin/chroot";
|
||||
}
|
||||
|
||||
if (!options.env) {
|
||||
options.env = {};
|
||||
}
|
||||
|
||||
if (!options.executor) {
|
||||
options.executor = {
|
||||
spawn: cp.spawn,
|
||||
};
|
||||
}
|
||||
|
||||
if (!options.logger) {
|
||||
options.logger = console;
|
||||
}
|
||||
|
||||
if (!options.global_flags) {
|
||||
options.global_flags = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic init
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async init(options = []) {
|
||||
const restic = this;
|
||||
let args = ["init", "--json"];
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
try {
|
||||
await restic.exec(restic.options.paths.restic, args);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err.code == 1 && err.stderr.includes("already")) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic unlock
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async unlock(options = []) {
|
||||
const restic = this;
|
||||
let args = ["unlock", "--json"];
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
try {
|
||||
await restic.exec(restic.options.paths.restic, args);
|
||||
return;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic backup
|
||||
*
|
||||
* @param {*} path
|
||||
* @param {*} options
|
||||
*/
|
||||
async backup(path, options = []) {
|
||||
const restic = this;
|
||||
let args = [];
|
||||
args = args.concat(["backup", "--json"]);
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
args = args.concat([path]);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "backup",
|
||||
timeout: 0,
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic tag
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async tag(options = []) {
|
||||
const restic = this;
|
||||
let args = [];
|
||||
args = args.concat(["tag", "--json"]);
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "tag",
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic snapshots
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshots(options = []) {
|
||||
const restic = this;
|
||||
let args = [];
|
||||
args = args.concat(["snapshots", "--json", "--no-lock"]);
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
restic.parseTagsFromArgs(args);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "snapshots",
|
||||
});
|
||||
|
||||
let snapshots = [];
|
||||
result.parsed.forEach((item) => {
|
||||
if (item.id) {
|
||||
snapshots.push(item);
|
||||
}
|
||||
|
||||
if (item.snapshots) {
|
||||
snapshots.push(...item.snapshots);
|
||||
}
|
||||
});
|
||||
|
||||
return snapshots;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic snapshots
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async snapshot_exists(snapshot_id) {
|
||||
const restic = this;
|
||||
const snapshots = await restic.snapshots([snapshot_id]);
|
||||
return snapshots.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* restic forget
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async forget(options = []) {
|
||||
const restic = this;
|
||||
let args = [];
|
||||
args = args.concat(["forget", "--json"]);
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "forget",
|
||||
});
|
||||
|
||||
return result.parsed;
|
||||
} catch (err) {
|
||||
if (err.code == 1 && err.stderr.includes("no such file or directory")) {
|
||||
return [];
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic stats
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async stats(options = []) {
|
||||
const restic = this;
|
||||
let args = [];
|
||||
args = args.concat(["stats", "--json", "--no-lock"]);
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "stats",
|
||||
timeout: 0, // can take a very long time to gather up details
|
||||
});
|
||||
|
||||
return result.parsed;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restic restore
|
||||
*
|
||||
* note that restore does not do any delete operations (ie: not like rsync --delete)
|
||||
*
|
||||
* @param {*} options
|
||||
*/
|
||||
async restore(options = []) {
|
||||
const restic = this;
|
||||
let args = ["restore", "--json", "--no-lock"];
|
||||
args = args.concat(restic.options.global_flags);
|
||||
args = args.concat(options);
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await restic.exec(restic.options.paths.restic, args, {
|
||||
operation: "restore",
|
||||
timeout: 0,
|
||||
});
|
||||
return result.parsed;
|
||||
} catch (err) {
|
||||
if (err.code == 1 && err.stderr.includes("Fatal:")) {
|
||||
const lines = err.stderr.split("\n").filter((item) => {
|
||||
return Boolean(String(item).trim());
|
||||
});
|
||||
const last_line = lines[lines.length - 1];
|
||||
const ingored_count = (err.stderr.match(/ignoring error/g) || [])
|
||||
.length;
|
||||
|
||||
restic.options.logger.info(
|
||||
`restic ignored error count: ${ingored_count}`
|
||||
);
|
||||
restic.options.logger.info(`restic stderr last line: ${last_line}`);
|
||||
|
||||
// if ignored count matches total count move on
|
||||
// "Fatal: There were 2484 errors"
|
||||
if (last_line.includes(String(ingored_count))) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
trimResultData(result, options = {}) {
|
||||
const trim_output_limt = options.max_entries || 50;
|
||||
// trim stdout/stderr/parsed lines to X number
|
||||
if (result.parsed && Array.isArray(result.parsed)) {
|
||||
result.parsed = result.parsed.slice(trim_output_limt * -1);
|
||||
}
|
||||
|
||||
result.stderr = result.stderr
|
||||
.split("\n")
|
||||
.slice(trim_output_limt * -1)
|
||||
.join("\n");
|
||||
|
||||
result.stdout = result.stdout
|
||||
.split("\n")
|
||||
.slice(trim_output_limt * -1)
|
||||
.join("\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
parseTagsFromArgs(args) {
|
||||
let tag_value_index;
|
||||
let tags = args.filter((value, index) => {
|
||||
if (String(value) == "--tag") {
|
||||
tag_value_index = index + 1;
|
||||
}
|
||||
return tag_value_index == index;
|
||||
});
|
||||
|
||||
tags = tags
|
||||
.map((value) => {
|
||||
if (value.includes(",")) {
|
||||
return value.split(",");
|
||||
}
|
||||
return [value];
|
||||
})
|
||||
.flat();
|
||||
return tags;
|
||||
}
|
||||
|
||||
exec(command, args, options = {}) {
|
||||
if (!options.hasOwnProperty("timeout")) {
|
||||
options.timeout = DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
const restic = this;
|
||||
args = args || [];
|
||||
|
||||
if (restic.options.sudo) {
|
||||
args.unshift(command);
|
||||
command = restic.options.paths.sudo;
|
||||
}
|
||||
|
||||
options.env = {
|
||||
...{},
|
||||
...process.env,
|
||||
...restic.options.env,
|
||||
...options.env,
|
||||
};
|
||||
|
||||
const cleansedLog = `${command} ${args.join(" ")}`;
|
||||
console.log("executing restic command: %s", cleansedLog);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let stdin;
|
||||
if (options.stdin) {
|
||||
stdin = options.stdin;
|
||||
delete options.stdin;
|
||||
}
|
||||
const child = restic.options.executor.spawn(command, args, options);
|
||||
if (stdin) {
|
||||
child.stdin.write(stdin);
|
||||
}
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let code_override;
|
||||
|
||||
const log_progress_output = _.debounce(
|
||||
(data) => {
|
||||
let snapshot_id;
|
||||
let path;
|
||||
switch (options.operation) {
|
||||
case "backup":
|
||||
snapshot_id = `unknown_creating_new_snapshot_in_progress`;
|
||||
path = args[args.length - 1];
|
||||
break;
|
||||
case "restore":
|
||||
snapshot_id = args
|
||||
.find((value) => {
|
||||
return String(value).includes(":");
|
||||
})
|
||||
.split(":")[0];
|
||||
|
||||
let path_index;
|
||||
path = args.find((value, index) => {
|
||||
if (String(value) == "--target") {
|
||||
path_index = index + 1;
|
||||
}
|
||||
return path_index == index;
|
||||
});
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.message_type == "status") {
|
||||
delete data.current_files;
|
||||
restic.options.logger.info(
|
||||
`restic ${options.operation} progress: snapshot_id=${snapshot_id}, path=${path}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
if (data.message_type == "summary") {
|
||||
restic.options.logger.info(
|
||||
`restic ${options.operation} summary: snapshot_id=${snapshot_id}, path=${path}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
},
|
||||
250,
|
||||
{ leading: true, trailing: true, maxWait: 5000 }
|
||||
);
|
||||
|
||||
child.stdout.on("data", function (data) {
|
||||
data = String(data);
|
||||
stdout += data;
|
||||
switch (options.operation) {
|
||||
case "backup":
|
||||
case "restore":
|
||||
try {
|
||||
let parsed = JSON.parse(data);
|
||||
log_progress_output(parsed);
|
||||
} catch (err) {}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr.on("data", function (data) {
|
||||
data = String(data);
|
||||
stderr += data;
|
||||
if (
|
||||
["forget", "snapshots"].includes(options.operation) &&
|
||||
stderr.includes("no such file or directory")
|
||||
) {
|
||||
// short-circut the operation vs waiting for all the retries
|
||||
// https://github.com/restic/restic/pull/2515
|
||||
switch (options.operation) {
|
||||
case "forget":
|
||||
code_override = 1;
|
||||
break;
|
||||
case "snapshots":
|
||||
code_override = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
|
||||
child.on("close", function (code) {
|
||||
const result = { code, stdout, stderr, timeout: false };
|
||||
|
||||
if (!result.parsed) {
|
||||
try {
|
||||
result.parsed = JSON.parse(result.stdout);
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
if (!result.parsed) {
|
||||
try {
|
||||
const lines = result.stdout.split("\n");
|
||||
const parsed = [];
|
||||
lines.forEach((line) => {
|
||||
if (!line) {
|
||||
return;
|
||||
}
|
||||
parsed.push(JSON.parse(line.trim()));
|
||||
});
|
||||
result.parsed = parsed;
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* normalize array responses in scenarios where not enough came through
|
||||
* to add newlines
|
||||
*/
|
||||
if (result.parsed && options.operation == "backup") {
|
||||
if (!Array.isArray(result.parsed)) {
|
||||
result.parsed = [result.parsed];
|
||||
}
|
||||
}
|
||||
|
||||
if (code == null && code_override != null) {
|
||||
code = code_override;
|
||||
}
|
||||
|
||||
// timeout scenario
|
||||
if (code === null) {
|
||||
result.timeout = true;
|
||||
reject(result);
|
||||
}
|
||||
|
||||
if (code) {
|
||||
reject(result);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Restic = Restic;
|
||||
Loading…
Reference in New Issue