Skip to main content
Go to Latest
File
import { ok } from "./util.ts";import * as T from "./types.ts";
export const DEFAULT_OPTIONS: T.BackupOptions = { delete: true, force: true, ignoreExisting: false, exclude: [],};type BackupSet = { srcPath: string; srcVolume: string; dstPath: string; dstVolume: string; options?: Partial<T.BackupOptions>;};/** * This utility simplifies the volume backup process. * ```ts * export const { createBackup, restoreBackup } = Backups.volumes("main").build(); * ``` * * Changing the options of the rsync, (ie exludes) use either * ```ts * Backups.volumes("main").set_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() * // or * Backups.with_options({exclude: ['bigdata/']}).volumes('excludedVolume').build() * ``` * * Using the more fine control, using the addSets for more control * ```ts * Backups.addSets({ * srcVolume: 'main', srcPath:'smallData/', dstPath: 'main/smallData/', dstVolume: : Backups.BACKUP * }, { * srcVolume: 'main', srcPath:'bigData/', dstPath: 'main/bigData/', dstVolume: : Backups.BACKUP, options: {exclude:['bigData/excludeThis']}} * ).build()q * ``` */export class Backups { static BACKUP = "BACKUP" as const;
constructor( private options = DEFAULT_OPTIONS, private backupSet = [] as BackupSet[], ) {} static volumes(...volumeNames: string[]) { return new Backups().addSets( ...volumeNames.map((srcVolume) => ({ srcVolume, srcPath: "./", dstPath: `./${srcVolume}/`, dstVolume: Backups.BACKUP, })), ); } static addSets(...options: BackupSet[]) { return new Backups().addSets(...options); } static with_options(options?: Partial<T.BackupOptions>) { return new Backups({ ...DEFAULT_OPTIONS, ...options }); } set_options(options?: Partial<T.BackupOptions>) { this.options = { ...this.options, ...options, }; return this; } volumes(...volumeNames: string[]) { return this.addSets( ...volumeNames.map((srcVolume) => ({ srcVolume, srcPath: "./", dstPath: `./${srcVolume}/`, dstVolume: Backups.BACKUP, })), ); } addSets(...options: BackupSet[]) { options.forEach((x) => this.backupSet.push({ ...x, options: { ...this.options, ...x.options } }), ); return this; } build() { const createBackup: T.ExpectedExports.createBackup = async (effects) => { const previousItems = ( await effects .readDir({ volumeId: Backups.BACKUP, path: ".", }) .catch(() => []) ).map((x) => `${x}`); const backupPaths = this.backupSet .filter((x: any) => x.dstVolume === Backups.BACKUP) .map((x) => x.dstPath) .map((x) => x.replace(/\.\/([^]*)\//, "$1")); const filteredItems = previousItems.filter( (x) => backupPaths.indexOf(x) === -1, ); for (const itemToRemove of filteredItems) { effects.error(`Trying to remove ${itemToRemove}`); await effects .removeDir({ volumeId: Backups.BACKUP, path: itemToRemove, }) .catch(() => effects.removeFile({ volumeId: Backups.BACKUP, path: itemToRemove, }), ) .catch(() => { effects.warn(`Failed to remove ${itemToRemove} from backup volume`); }); } for (const item of this.backupSet) { if (notEmptyPath(item.dstPath)) { await effects.createDir({ volumeId: item.dstVolume, path: item.dstPath, }); } await effects .runRsync({ ...item, options: { ...this.options, ...item.options, }, }) .wait(); } return ok; }; const restoreBackup: T.ExpectedExports.restoreBackup = async (effects) => { for (const item of this.backupSet) { if (notEmptyPath(item.srcPath)) { await effects.createDir({ volumeId: item.srcVolume, path: item.srcPath, }); } await effects .runRsync({ options: { ...this.options, ...item.options, }, srcVolume: item.dstVolume, dstVolume: item.srcVolume, srcPath: item.dstPath, dstPath: item.srcPath, }) .wait(); } return ok; }; return { createBackup, restoreBackup }; }}function notEmptyPath(file: string) { return ["", ".", "./"].indexOf(file) === -1;}