UNPKG

vmsnap

Version:

A Node based backup and backup rotation tool for KVM domains.

2 lines (1 loc) 3.48 kB
import{sep as c}from"path";import{spawn as A}from"child_process";import{rm as U}from"fs/promises";import a from"dayjs";import O from"dayjs/plugin/advancedFormat.js";import v from"dayjs/plugin/quarterOfYear.js";import x from"dayjs/plugin/dayOfYear.js";import{logger as i,ERR_DOMAINS as h,ERR_OUTPUT_DIR as _}from"../vmsnap.js";import{cleanupCheckpoints as L,domainExists as M,isDomainRunning as C,fetchAllDomains as P}from"./virsh.js";import{createError as Y,fileExists as E,parseArrayParam as T}from"./general.js";import{cleanupBitmaps as Q}from"./qemu-img.js";a.extend(O),a.extend(v),a.extend(x);export const BACKUP="virtnbdbackup";const R="YYYY-MM",F="YYYY-[Q]Q",y="YYYY-",g="YYYY";export const FREQUENCY_MONTHLY="month";const k="quarter",$="bi-annual",b="year",N=[FREQUENCY_MONTHLY,k,$,b],p=(t=FREQUENCY_MONTHLY,r=!0)=>{let s;switch(t){case k:s=r?a().format(F):a().subtract(3,"months").format(F);break;case $:let e=a().dayOfYear()>=180?2:1;r||(e=e===1?2:1);const o=`${y}p${e}`;s=r?a().format(o):a().subtract(6,"months").format(o);break;case b:s=r?a().format(g):a().subtract(1,"year").format(g);break;case FREQUENCY_MONTHLY:s=r?a().format(R):a().subtract(1,"month").format(R);break;default:return}return`vmsnap-backup-${t}ly-${s}`},D=async({domains:t,output:r,raw:s,groupBy:e,prune:o,connect:l,socketfile:n})=>{if(!t)throw Y("No domains specified",h);if(!r)throw Y("No output directory specified",_);const m=await T(t,P);if(m.length===0)throw Y(`No matching domains found for: ${t}`,h);for(const f of m)await I(f,e,r)&&(i.info("Creating a new backup directory, running bitmap cleanup"),await L(f),await Q(f)),await H(f,r,s,e,l,n),await q(f,e,o,r)&&(i.info("Middle of the current backup window, running a cleanup on old backups"),await S(f,e,r))},I=async(t,r,s)=>await E(`${s}${c}${t}${c}${p(r)}`)?!1:(i.info("Backup folder does not exist, cleanup required"),!0),q=async(t,r,s,e)=>{if(s===!1)return!1;if(!N.includes(r))return i.warn(`Invalid groupBy: ${r}. Pruning disabled`),!1;const o=p(r,!1);if(console.log(o),o===void 0)return i.info("Unable to determine previous backup folder, skipping pruning"),!1;if(!await E(`${e}${c}${t}${c}${o}`))return!1;const n=a().diff(B(r),"days");switch(i.info(`Days since the start of the ${r}: ${n}`),r.toLowerCase()){case FREQUENCY_MONTHLY:return n>=15;case k:return n>=45;case $:return n>=90;case b:return n>=180;default:return!1}},S=async(t,r,s)=>{const e=p(r,!1);if(e===void 0){i.info("Unable to determine previous backup folder, skipping pruning");return}i.info(`Pruning ${r}ly backup (${e}) for ${t}`),await U(`${s}${c}${t}${c}${e}`,{recursive:!0,force:!0})},B=t=>t===$?a().startOf(FREQUENCY_MONTHLY).subtract(6,"months"):N.includes(t)?a().startOf(t):a().startOf(FREQUENCY_MONTHLY),H=async(t,r,s,e,o,l)=>{if(!await M(t)){i.warn(`${t} does not exist`);return}const n=["--noprogress","-d",t,"-l","auto","-o",`${r}${c}${t}${c}${p(e)}`];s&&n.push("--raw"),o&&n.push("-U",o);const m=l||`/var/lib/libvirt/qemu/virtnbdbackup.${process.pid}`;n.push("-f",m),await C(t)||(i.info(`${t} is offline, starting in paused state for checkpoint backup`),n.push("-S"));const u=A(BACKUP,n,{uid:process.getuid(),gid:process.getgid(),stdio:"inherit"});u.stdout&&(u.stdout.setEncoding("utf8"),u.stdout.on("data",d=>{i.info(d)})),u.stderr&&(u.stderr.setEncoding("utf8"),u.stderr.on("data",d=>{i.error(d)})),await new Promise(d=>{u.on("close",w=>{w!==0&&i.error(`Backup for ${t} failed with code ${w}`),d()})})};export{p as getBackupFolder,D as performBackup};