Initial commit
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
cpSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
} from "node:fs";
|
||||
import { extname, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { spawn } from "node:child_process";
|
||||
import process from "node:process";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
function loadConfig() {
|
||||
const configPath = process.env.ICESHRIMP_CONFIG ?? ".config/default.yml";
|
||||
const config = existsSync(configPath)
|
||||
? yaml.load(readFileSync(configPath, "utf8")) ?? {}
|
||||
: {};
|
||||
const db = config.db ?? {};
|
||||
|
||||
return {
|
||||
mediaDir: process.env.ICESHRIMP_MEDIA_DIR ?? "files",
|
||||
db: {
|
||||
host: process.env.PGHOST ?? db.host ?? "localhost",
|
||||
port: String(process.env.PGPORT ?? db.port ?? 5432),
|
||||
database: process.env.PGDATABASE ?? db.db ?? db.database ?? "iceshrimp",
|
||||
user: process.env.PGUSER ?? db.user ?? "postgres",
|
||||
password: process.env.PGPASSWORD ?? db.pass ?? db.password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function run(command, args, env, options = {}) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
stdio: "inherit",
|
||||
env,
|
||||
...options,
|
||||
});
|
||||
child.on("error", reject);
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0) resolvePromise();
|
||||
else reject(new Error(`${command} exited with code ${code}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function restoreDatabase(backupFile, db, env) {
|
||||
if (extname(backupFile) === ".sql") {
|
||||
await run("psql", ["--dbname", db.database, "--file", backupFile], env);
|
||||
} else {
|
||||
await run(
|
||||
"pg_restore",
|
||||
[
|
||||
"--clean",
|
||||
"--if-exists",
|
||||
"--no-owner",
|
||||
"--dbname",
|
||||
db.database,
|
||||
backupFile,
|
||||
],
|
||||
env,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const input = process.argv[2];
|
||||
if (!input) {
|
||||
console.error("Usage: yarn full:restore <backup.tar.gz|backup.dump|backup.sql>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const backupFile = resolve(input);
|
||||
if (!existsSync(backupFile)) {
|
||||
console.error(`Backup file not found: ${backupFile}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = loadConfig();
|
||||
const env = {
|
||||
...process.env,
|
||||
PGHOST: config.db.host,
|
||||
PGPORT: config.db.port,
|
||||
PGDATABASE: config.db.database,
|
||||
PGUSER: config.db.user,
|
||||
};
|
||||
if (config.db.password != null) env.PGPASSWORD = config.db.password;
|
||||
|
||||
if (backupFile.endsWith(".tar.gz") || backupFile.endsWith(".tgz")) {
|
||||
const workDir = mkdtempSync(resolve(tmpdir(), "iceshrimp-restore-"));
|
||||
try {
|
||||
await run("tar", ["-xzf", backupFile, "-C", workDir], process.env);
|
||||
|
||||
const manifestPath = resolve(workDir, "manifest.json");
|
||||
if (existsSync(manifestPath)) {
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
if (manifest.type !== "iceshrimp-full-backup") {
|
||||
throw new Error("Archive is not an Iceshrimp full backup");
|
||||
}
|
||||
}
|
||||
|
||||
const dumpPath = resolve(workDir, "database.dump");
|
||||
if (!existsSync(dumpPath)) {
|
||||
throw new Error("Archive does not contain database.dump");
|
||||
}
|
||||
await restoreDatabase(dumpPath, config.db, env);
|
||||
|
||||
const filesPath = resolve(workDir, "files");
|
||||
if (existsSync(filesPath)) {
|
||||
const mediaDir = resolve(config.mediaDir);
|
||||
mkdirSync(mediaDir, { recursive: true });
|
||||
cpSync(filesPath, mediaDir, {
|
||||
recursive: true,
|
||||
dereference: false,
|
||||
});
|
||||
console.log(`Media files restored to ${mediaDir}`);
|
||||
}
|
||||
|
||||
const configPath = resolve(workDir, "config");
|
||||
if (existsSync(configPath)) {
|
||||
const restoredConfigDir = resolve(".config/restored-backup");
|
||||
mkdirSync(restoredConfigDir, { recursive: true });
|
||||
cpSync(configPath, restoredConfigDir, {
|
||||
recursive: true,
|
||||
dereference: false,
|
||||
});
|
||||
console.log(`Backup config files copied to ${restoredConfigDir}`);
|
||||
}
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
}
|
||||
} else {
|
||||
await restoreDatabase(backupFile, config.db, env);
|
||||
}
|
||||
|
||||
console.log(`Backup restored from ${backupFile}`);
|
||||
Reference in New Issue
Block a user