Initial commit

This commit is contained in:
2026-07-26 12:29:43 +09:00
parent 5f290dc465
commit 50bfaeafdf
2253 changed files with 318636 additions and 196 deletions
+143
View File
@@ -0,0 +1,143 @@
import {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
statSync,
writeFileSync,
} from "node:fs";
import { basename, dirname, 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 {
configPath,
secretsPath: process.env.ICESHRIMP_SECRETS,
url: config.url,
version: JSON.parse(readFileSync("packages/meta.json", "utf8")).version,
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 timestamp() {
const now = new Date();
const pad = (value) => value.toString().padStart(2, "0");
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(
now.getDate(),
)}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
}
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}`));
});
});
}
const config = loadConfig();
const outFile = resolve(
process.argv[2] ?? `backups/iceshrimp-full-${timestamp()}.tar.gz`,
);
const workDir = mkdtempSync(resolve(tmpdir(), "iceshrimp-backup-"));
mkdirSync(dirname(outFile), { recursive: true });
try {
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;
await run(
"pg_dump",
[
"--format=custom",
"--blobs",
"--no-owner",
"--file",
resolve(workDir, "database.dump"),
config.db.database,
],
env,
);
const configDir = resolve(workDir, "config");
mkdirSync(configDir, { recursive: true });
for (const path of [config.configPath, config.secretsPath].filter(Boolean)) {
if (existsSync(path)) cpSync(path, resolve(configDir, basename(path)));
}
const mediaDir = resolve(config.mediaDir);
const outDir = dirname(outFile);
let includedMedia = false;
if (
existsSync(mediaDir) &&
outDir !== mediaDir &&
!outDir.startsWith(`${mediaDir}/`)
) {
cpSync(mediaDir, resolve(workDir, "files"), {
recursive: true,
dereference: false,
});
includedMedia = true;
}
writeFileSync(
resolve(workDir, "manifest.json"),
`${JSON.stringify(
{
type: "iceshrimp-full-backup",
version: config.version,
createdAt: new Date().toISOString(),
url: config.url,
database: config.db.database,
included: {
database: true,
config: true,
media: includedMedia,
},
restore: "yarn full:restore <this archive>",
},
null,
2,
)}\n`,
"utf8",
);
await run("tar", ["-czf", outFile, "-C", workDir, "."], process.env);
console.log(
`Full backup written to ${outFile} (${statSync(outFile).size} bytes)`,
);
} finally {
rmSync(workDir, { recursive: true, force: true });
}