-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackup.js
167 lines (147 loc) · 4.54 KB
/
backup.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
const fs = require("fs").promises;
const { join } = require("path");
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const cron = require("node-cron");
const tar = require("tar");
const dayjs = require("dayjs");
const { kebabCase } = require("lodash");
const { createLogger, format, transports } = require("winston");
const logger = createLogger({
level: "debug",
format: format.combine(
format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
format.prettyPrint({
depth: 10,
})
),
transports: [
new transports.Console({
format: format.combine(format.colorize(), format.simple()),
}),
new transports.File({ filename: "backup.log" }),
],
});
const bucketName = process.env.AWS_BUCKET;
const region = process.env.AWS_DEFAULT_REGION;
const schedule = process.env.BACKUP_SCHEDULE || "* * * * *";
const uploadToS3 = process.env.BACKUP_UPLOAD_TO_S3_ENABLED === "true";
const appDirectory = process.env.APP_DIR;
const backupDir = process.env.BACKUP_DIR || "/persistent/backup";
const configFile = process.env.CONFIG_FILE || "strapi.config.json";
const appFullname = process.env.APP_FULLNAME;
const backupPathPrefix = process.env.BACKUP_PATH_PREFIX;
async function backupRoutine() {
try {
logger.info("Starting backup cycle");
const start = dayjs();
const filenames = [
"api",
"public",
"config",
"components",
"strapi.config.json",
];
// dump current strapi config to $BACKUP_DIR
logger.info("Dumping strapi config file to backup directory");
try {
await exec(
`yarn strapi configuration:dump --pretty --file ${appDirectory}/${configFile}`,
{
cwd: appDirectory,
}
);
logger.info("Dumped strapi config file");
} catch (err) {
logger.error(
"Failed to dump config, falling back to existing config file"
);
try {
await fs.access(join(appDirectory, configFile));
logger.info("Pre-dumped config exists, using this file as fallback");
} catch (err) {
logger.error(
"Pre-dumped config does NOT exist, falling back to empty JSON file"
);
await fs.writeFile(join(appDirectory, configFile), "{}", {
encoding: "utf-8",
});
}
}
// directory to write the archive to
const archiveDirectory = backupDir;
// archive with timestamp as unix
const archiveName =
kebabCase(`${appFullname}-backup-${start.unix()}`) + ".tgz";
// create a tarball w/ gzip in $BACKUP_DIR/archives/
await tar.create(
{
gzip: true,
cwd: appDirectory,
file: join(backupDir, archiveName),
},
filenames
);
logger.info(`Bundled tarball to ${archiveName}`, {
dir: archiveDirectory,
file: archiveName,
});
// S3 upload of archive
if (uploadToS3) {
if (!bucketName) {
throw new Error("Missing bucket name");
}
const archiveBuffer = await fs.readFile(
join(archiveDirectory, archiveName)
);
const uploadParams = {
Bucket: bucketName,
Key: backupPathPrefix + "/" + archiveName,
Body: archiveBuffer,
ContentType: "application/gzip",
};
const s3 = new S3Client({
region: region,
});
await s3.send(new PutObjectCommand(uploadParams));
logger.info("Uploaded archive", {
name: archiveName,
prefix: backupPathPrefix,
dir: backupDir,
});
} else {
logger.info("UploadToS3 option is not specified, skipping...");
}
// move backup archive to "latest"
logger.info("Replacing :latest archive");
await fs.rename(
join(archiveDirectory, archiveName), // temporary one with timestamp
join(archiveDirectory, kebabCase(`${appFullname}-backup-latest`) + ".tgz") // latest one
);
const end = dayjs();
logger.info("Finished backup run", {
duration: end.diff(start, "seconds"),
unit: "seconds",
});
} catch (err) {
logger.error(err.message, { err });
process.exit(1);
}
}
/**
* Main worker - runs the backup routine on a given schedule
*/
cron.schedule(schedule, backupRoutine);
logger.info(`Added cronjob with schedule ${schedule}`);
backupRoutine()
.then(() => {
logger.info("Completed initial out-of-order backup run");
})
.catch((err) => {
logger.error("Failed to run initial backup run, aborting startup...");
logger.debug({ err });
process.exit(1);
});