core/steps/backup.step.ts
core/steps/backup.step.ts is a file in Bane's Lab Deploy. 91 lines of code and 14 definitions.
import {
ARCHIVE_FAILED,
ARCHIVE_UNIT,
BACKED_UP,
BACKING_UP,
BACKUP_REMOVED,
BACKUP_SAVED,
DOWNLOADING_BACKUP,
DOWNLOAD_LABEL,
EXTRACTING,
PRUNED,
PRUNING,
REMOTE_BACKUP_REMOVED,
REUPLOADING,
REUPLOAD_LABEL,
ROLLED_BACK,
ROLLING_BACK,
} from "#configuration/strings/deployment.strings";
import {
ARCHIVE_SUFFIX,
BACKUP_PREFIX,
BACKUP_RETENTION,
PATH_SEPARATOR,
REMOTE_SITE,
REMOTE_STAGING,
} from "#configuration/constants/deployment.constants";
import type { Journal, Shell } from "#types/deployment.types";
import { mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
import { absolutePath } from "@ssot/paths";
import { createByteProgress } from "#core/reporters/journal.reporter";
import { join } from "node:path";
import { stamp } from "#core/converters/text.converter";
const backupDirectory = function backupDirectory(): string {
const directory = absolutePath("app.backups");
mkdirSync(directory, { recursive: true });
return directory;
};
const staged = function staged(name: string): string {
return REMOTE_STAGING + PATH_SEPARATOR + name;
};
export const archiveSite = async function archiveSite(shell: Shell, journal: Journal): Promise<string> {
journal.log(BACKING_UP);
const name = BACKUP_PREFIX + stamp() + ARCHIVE_SUFFIX;
const remote = staged(name);
const result = await shell.run(
`mkdir -p ${REMOTE_SITE} ${REMOTE_STAGING} && tar -czf ${remote} -C ${REMOTE_SITE} .`,
);
if (result.code !== 0) {
throw new Error(ARCHIVE_FAILED + result.stderr);
}
if (result.stderr.length > 0) {
journal.log(result.stderr);
}
journal.mark(BACKED_UP);
journal.log(DOWNLOADING_BACKUP);
const local = join(backupDirectory(), name);
const download = createByteProgress(journal, DOWNLOAD_LABEL, ARCHIVE_UNIT);
await shell.download(local, remote, download.step);
download.finish();
journal.mark(BACKUP_SAVED + local);
await shell.run(`rm -f ${remote}`);
journal.log(REMOTE_BACKUP_REMOVED);
return name;
};
const isArchive = function isArchive(name: string): boolean {
return name.startsWith(BACKUP_PREFIX) && name.endsWith(ARCHIVE_SUFFIX);
};
export const pruneArchives = function pruneArchives(journal: Journal): void {
journal.log(PRUNING + String(BACKUP_RETENTION));
const directory = backupDirectory();
const archives = readdirSync(directory)
.filter(isArchive)
.toSorted((a, b) => statSync(join(directory, b)).mtimeMs - statSync(join(directory, a)).mtimeMs);
for (const stale of archives.slice(BACKUP_RETENTION)) {
unlinkSync(join(directory, stale));
journal.log(BACKUP_REMOVED + stale);
}
journal.mark(PRUNED);
};
export const restoreSite = async function restoreSite(shell: Shell, journal: Journal, name: string): Promise<void> {
journal.log(ROLLING_BACK);
const remote = staged(name);
journal.log(REUPLOADING + name);
await shell.run(`mkdir -p ${REMOTE_STAGING}`);
const upload = createByteProgress(journal, REUPLOAD_LABEL, ARCHIVE_UNIT);
await shell.upload(join(backupDirectory(), name), remote, upload.step);
upload.finish();
journal.log(EXTRACTING);
await shell.run(`mkdir -p ${REMOTE_SITE} && cd ${REMOTE_SITE} && tar -xzf ${remote} && rm -f ${remote}`);
journal.log(ROLLED_BACK);
};