Backup: Create temporary folder to storage files/folders #345
@@ -14,23 +14,24 @@
|
|||||||
"source=bdscore_dind,target=/var/lib/docker,type=volume"
|
"source=bdscore_dind,target=/var/lib/docker,type=volume"
|
||||||
],
|
],
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"GitHub.copilot-nightly",
|
"GitHub.copilot-nightly",
|
||||||
"GitHub.copilot-labs",
|
"GitHub.copilot-labs",
|
||||||
"benshabatnoam.google-translate-ext",
|
"benshabatnoam.google-translate-ext",
|
||||||
"eamodio.gitlens",
|
"eamodio.gitlens",
|
||||||
"github.vscode-pull-request-github",
|
"github.vscode-pull-request-github",
|
||||||
"visualstudioexptteam.vscodeintellicode",
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
"ms-vscode-remote.remote-containers",
|
"ms-vscode-remote.remote-containers",
|
||||||
"wix.vscode-import-cost",
|
"wix.vscode-import-cost",
|
||||||
"eg2.vscode-npm-script",
|
"eg2.vscode-npm-script",
|
||||||
"christian-kohler.npm-intellisense",
|
"christian-kohler.npm-intellisense",
|
||||||
"christian-kohler.path-intellisense",
|
"christian-kohler.path-intellisense",
|
||||||
"aaron-bond.better-comments",
|
"aaron-bond.better-comments",
|
||||||
"vscode-icons-team.vscode-icons",
|
"vscode-icons-team.vscode-icons",
|
||||||
"me-dutour-mathieu.vscode-github-actions",
|
"me-dutour-mathieu.vscode-github-actions",
|
||||||
"cschleiden.vscode-github-actions",
|
"cschleiden.vscode-github-actions",
|
||||||
"oderwat.indent-rainbow",
|
"oderwat.indent-rainbow",
|
||||||
"ms-azuretools.vscode-docker"
|
"ms-azuretools.vscode-docker",
|
||||||
]
|
"formulahendry.code-runner"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
src/**/*.d.ts
|
src/**/*.d.ts
|
||||||
src/**/*.d.js
|
src/**/*.d.js
|
||||||
|
backup_*.zip
|
@@ -1 +1,2 @@
|
|||||||
!dist/
|
!dist/
|
||||||
|
backup_*.zip
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"github.copilot"
|
"github.copilot",
|
||||||
|
"formulahendry.code-runner"
|
||||||
]
|
]
|
||||||
}
|
}
|
113
src/backup.ts
113
src/backup.ts
@@ -9,33 +9,120 @@ import simpleGit from "simple-git";
|
|||||||
const ServerPathRoot = path.resolve(process.env.SERVER_PATH||path.join(os.homedir(), "bds_core/servers"));
|
const ServerPathRoot = path.resolve(process.env.SERVER_PATH||path.join(os.homedir(), "bds_core/servers"));
|
||||||
const backupFolderPath = path.resolve(process.env.BACKUP_PATH||path.join(os.homedir(), "bds_core/backups"));
|
const backupFolderPath = path.resolve(process.env.BACKUP_PATH||path.join(os.homedir(), "bds_core/backups"));
|
||||||
|
|
||||||
export default CreateBackup;
|
async function createTempFolder() {
|
||||||
export async function CreateBackup(WriteFile: {path: string}|true|false = false) {
|
let cleaned = false;
|
||||||
if (!(fs.existsSync(backupFolderPath))) await fsPromise.mkdir(backupFolderPath, {recursive: true});
|
const tempFolderPath = path.join(os.tmpdir(), Buffer.from(Math.random().toString()).toString("hex")+"tmpFolder");
|
||||||
|
if (fs.existsSync(tempFolderPath)) await fse.rm(tempFolderPath, {recursive: true});
|
||||||
|
await fsPromise.mkdir(tempFolderPath, { recursive: true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add file to temp Folder
|
||||||
|
*
|
||||||
|
* @param filePath - Original file path
|
||||||
|
* @param onStorage - on Storage temp file path, example: serverName/fileName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addFile = async (filePath: string, onStorage?: string) => {
|
||||||
|
if (cleaned) throw new Error("Cannot add file after cleaning");
|
||||||
|
if (onStorage === undefined) onStorage = path.parse(filePath).name;
|
||||||
|
const onTempStorage = path.join(tempFolderPath, onStorage);
|
||||||
|
const basenameFolder = path.parse(onTempStorage).dir;
|
||||||
|
await fsPromise.mkdir(basenameFolder, { recursive: true }).catch(() => undefined);
|
||||||
|
await fsPromise.copyFile(filePath, onTempStorage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add folder to temp Folder (include subfolders)
|
||||||
|
*
|
||||||
|
* @param folderPath - Original folder path
|
||||||
|
* @param onStorage - on Storage temp folder path, example: serverName/folderName
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const addFolder = async (folderPath: string, onStorage: string = path.basename(folderPath)) => {
|
||||||
|
if (cleaned) throw new Error("Cannot add folder after cleaning");
|
||||||
|
if (!(fs.existsSync(folderPath) && fs.lstatSync(folderPath).isDirectory())) throw new Error(`${folderPath} is not a folder`);
|
||||||
|
await fse.copy(folderPath, path.join(tempFolderPath, onStorage), {recursive: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get only files from temp folder recursively
|
||||||
|
*
|
||||||
|
* @returns list files
|
||||||
|
*/
|
||||||
|
const listFiles = async () => {
|
||||||
|
if (cleaned) throw new Error("Cannot list files after cleaning");
|
||||||
|
const listFolder = async (folderPath: string) => {
|
||||||
|
const folderFiles = (await fsPromise.readdir(folderPath)).filter(file => !(file === ".git")).map(file => path.join(folderPath, file));
|
||||||
|
for (const file of folderFiles) {
|
||||||
|
const FileStats = await fsPromise.lstat(file).catch(() => null);
|
||||||
|
if (FileStats === null) {}
|
||||||
|
else if (FileStats.isDirectory()) folderFiles.push(...(await listFolder(file)));
|
||||||
|
else if (FileStats.isSymbolicLink()){};
|
||||||
|
}
|
||||||
|
return folderFiles;
|
||||||
|
}
|
||||||
|
const FilesMaped = (await listFolder(tempFolderPath)).filter(a => !(fs.lstatSync(a).isDirectory())).map(PathF => PathF.replace(tempFolderPath+path.sep, ""));
|
||||||
|
return FilesMaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove temp folder and lock to add new files and folders
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const cleanFolder = async () => {
|
||||||
|
if (cleaned) throw new Error("Cannot clean folder after cleaning");
|
||||||
|
await fse.rm(tempFolderPath, {recursive: true, force: true});
|
||||||
|
cleaned = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
tempFolderPath,
|
||||||
|
addFile,
|
||||||
|
addFolder,
|
||||||
|
listFiles,
|
||||||
|
cleanFolder
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genericAddFiles() {
|
||||||
// Create empty zip Buffer
|
// Create empty zip Buffer
|
||||||
const BackupZip = new AdmZip();
|
const TempFolder = await createTempFolder()
|
||||||
|
|
||||||
// List all Servers
|
// List all Servers
|
||||||
for (const __Server_Path of fs.readdirSync(ServerPathRoot).filter(Server => !!bdsCoretypes.PlatformArray.find(Platform => Platform === Server))) {
|
for (const __Server_Path of fs.readdirSync(ServerPathRoot).filter(Server => !!bdsCoretypes.PlatformArray.find(Platform => Platform === Server))) {
|
||||||
const Platform = __Server_Path as bdsCoretypes.Platform;
|
const Platform = __Server_Path as bdsCoretypes.Platform;
|
||||||
const ServerPath = path.join(ServerPathRoot, __Server_Path);
|
const ServerPath = path.join(ServerPathRoot, __Server_Path);
|
||||||
if (fs.existsSync(ServerPath)) {
|
if (fs.existsSync(ServerPath)) {
|
||||||
if (fs.existsSync(path.join(ServerPath, "worlds"))) BackupZip.addLocalFolder(await fsPromise.realpath(path.join(ServerPath, "worlds")), Platform+"/worlds");
|
if (fs.existsSync(path.join(ServerPath, "worlds"))) await TempFolder.addFolder(await fsPromise.realpath(path.join(ServerPath, "worlds")), Platform+"/worlds");
|
||||||
if (fs.existsSync(path.join(ServerPath, "server.properties"))) BackupZip.addLocalFile(await fsPromise.realpath(path.join(ServerPath, "server.properties")), Platform+"/server.properties");
|
if (fs.existsSync(path.join(ServerPath, "server.properties"))) await TempFolder.addFile(await fsPromise.realpath(path.join(ServerPath, "server.properties")), Platform+"/server.properties");
|
||||||
if (fs.existsSync(path.join(ServerPath, "permissions.json"))) BackupZip.addLocalFile(path.join(ServerPath, "permissions.json"), Platform+"/permissions.json");
|
if (fs.existsSync(path.join(ServerPath, "permissions.json"))) await TempFolder.addFile(path.join(ServerPath, "permissions.json"), Platform+"/permissions.json");
|
||||||
if (Platform === "java") {
|
if (Platform === "java") {
|
||||||
if (fs.existsSync(path.join(ServerPath, "banned-ips.json"))) BackupZip.addLocalFile(path.join(ServerPath, "banned-ips.json"), Platform+"/banned-ips.json");
|
if (fs.existsSync(path.join(ServerPath, "banned-ips.json"))) await TempFolder.addFile(path.join(ServerPath, "banned-ips.json"), Platform+"/banned-ips.json");
|
||||||
if (fs.existsSync(path.join(ServerPath, "banned-players.json"))) BackupZip.addLocalFile(path.join(ServerPath, "banned-players.json"), Platform+"/banned-players.json");
|
if (fs.existsSync(path.join(ServerPath, "banned-players.json"))) await TempFolder.addFile(path.join(ServerPath, "banned-players.json"), Platform+"/banned-players.json");
|
||||||
if (fs.existsSync(path.join(ServerPath, "whitelist.json"))) BackupZip.addLocalFile(path.join(ServerPath, "whitelist.json"), Platform+"/whitelist.json");
|
if (fs.existsSync(path.join(ServerPath, "whitelist.json"))) await TempFolder.addFile(path.join(ServerPath, "whitelist.json"), Platform+"/whitelist.json");
|
||||||
// Filter folders
|
// Filter folders
|
||||||
const Folders = fs.readdirSync(ServerPath).filter(Folder => fs.lstatSync(path.join(ServerPath, Folder)).isDirectory()).filter(a => !(a === "libraries"||a === "logs"||a === "versions"));
|
const Folders = fs.readdirSync(ServerPath).filter(Folder => fs.lstatSync(path.join(ServerPath, Folder)).isDirectory()).filter(a => !(a === "libraries"||a === "logs"||a === "versions"));
|
||||||
for (const world of Folders) BackupZip.addLocalFolder(path.join(ServerPath, world), Platform+"/"+world);
|
for (const world of Folders) await TempFolder.addFolder(path.join(ServerPath, world), Platform+"/"+world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return TempFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateBackup;
|
||||||
|
export async function CreateBackup(WriteFile: {path: string}|true|false = false) {
|
||||||
|
if (!(fs.existsSync(backupFolderPath))) await fsPromise.mkdir(backupFolderPath, {recursive: true});
|
||||||
|
// Add Folders and files
|
||||||
|
const TempFolder = await genericAddFiles()
|
||||||
|
// Create empty zip Buffer
|
||||||
|
const zip = new AdmZip();
|
||||||
|
for (const file of await TempFolder.listFiles()) zip.addLocalFile(path.join(TempFolder.tempFolderPath, file), (path.sep+path.parse(file).dir));
|
||||||
|
await TempFolder.cleanFolder();
|
||||||
// Get Zip Buffer
|
// Get Zip Buffer
|
||||||
const zipBuffer = BackupZip.toBuffer();
|
const zipBuffer = zip.toBuffer();
|
||||||
if (typeof WriteFile === "object") {
|
if (typeof WriteFile === "object") {
|
||||||
let BackupFile = path.resolve(backupFolderPath, `${new Date().toString().replace(/[-\(\)\:\s+]/gi, "_")}.zip`);
|
let BackupFile = path.resolve(backupFolderPath, `${new Date().toString().replace(/[-\(\)\:\s+]/gi, "_")}.zip`);
|
||||||
if (!!WriteFile.path) BackupFile = path.resolve(WriteFile.path);
|
if (!!WriteFile.path) BackupFile = path.resolve(WriteFile.path);
|
||||||
@@ -117,4 +204,4 @@ export async function gitBackup(Platform: bdsCoretypes.Platform, options?: gitBa
|
|||||||
if (commit) await gitLocal.commit(`${Platform} backup - ${(new Date()).toISOString()}`);
|
if (commit) await gitLocal.commit(`${Platform} backup - ${(new Date()).toISOString()}`);
|
||||||
if (!!options&&!!options.pushCommits) await gitLocal.push();
|
if (!!options&&!!options.pushCommits) await gitLocal.push();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -5,20 +5,22 @@ import { isValidCron } from "cron-validator";
|
|||||||
import * as BdsCore from "../index";
|
import * as BdsCore from "../index";
|
||||||
import * as bdsTypes from "../globalType";
|
import * as bdsTypes from "../globalType";
|
||||||
import cli_color from "cli-color";
|
import cli_color from "cli-color";
|
||||||
|
import path from "path";
|
||||||
|
import { promises as fsPromise } from "fs";
|
||||||
|
|
||||||
const Yargs = yargs(process.argv.slice(2)).option("platform", {
|
const Yargs = yargs(process.argv.slice(2)).command("download", "Download and Install server", yargs => {
|
||||||
alias: "p",
|
|
||||||
describe: "Bds Core Platform",
|
|
||||||
demandOption: true,
|
|
||||||
type: "string",
|
|
||||||
choices: ["bedrock", "java", "pocketmine", "spigot", "dragonfly"],
|
|
||||||
default: "bedrock"
|
|
||||||
}).command("download", "Download and Install server", yargs => {
|
|
||||||
const options = yargs.option("version", {
|
const options = yargs.option("version", {
|
||||||
alias: "v",
|
alias: "v",
|
||||||
describe: "Server Version",
|
describe: "Server Version",
|
||||||
demandOption: true,
|
demandOption: true,
|
||||||
type: "string"
|
type: "string"
|
||||||
|
}).option("platform", {
|
||||||
|
alias: "p",
|
||||||
|
describe: "Bds Core Platform",
|
||||||
|
demandOption: true,
|
||||||
|
type: "string",
|
||||||
|
choices: ["bedrock", "java", "pocketmine", "spigot", "dragonfly"],
|
||||||
|
default: "bedrock"
|
||||||
}).parseSync();
|
}).parseSync();
|
||||||
const Platform = options.platform as bdsTypes.Platform;
|
const Platform = options.platform as bdsTypes.Platform;
|
||||||
console.log("Starting Download...");
|
console.log("Starting Download...");
|
||||||
@@ -26,8 +28,25 @@ const Yargs = yargs(process.argv.slice(2)).option("platform", {
|
|||||||
console.log("Sucess to download server");
|
console.log("Sucess to download server");
|
||||||
console.info("Release date: %s", `${res.Date.getDate()}/${res.Date.getMonth()+1}/${res.Date.getFullYear()}`);
|
console.info("Release date: %s", `${res.Date.getDate()}/${res.Date.getMonth()+1}/${res.Date.getFullYear()}`);
|
||||||
});
|
});
|
||||||
|
}).command("backup", "Create Backups", async yargs => {
|
||||||
|
const {storage} = yargs.option("storage", {
|
||||||
|
alias: "s",
|
||||||
|
describe: "Storage Path",
|
||||||
|
demandOption: false,
|
||||||
|
type: "string",
|
||||||
|
default: path.join(process.cwd(), "backup_"+new Date().toString().replace(/[-\(\)\:\s+]/gi, "_"))+".zip"
|
||||||
|
}).parseSync();
|
||||||
|
const zipBuffer = await BdsCore.Backup.CreateBackup(false);
|
||||||
|
await fsPromise.writeFile(storage, zipBuffer);
|
||||||
}).command("start", "Start Server", async yargs => {
|
}).command("start", "Start Server", async yargs => {
|
||||||
const options = await yargs.option("cronBackup", {
|
const options = await yargs.option("platform", {
|
||||||
|
alias: "p",
|
||||||
|
describe: "Bds Core Platform",
|
||||||
|
demandOption: true,
|
||||||
|
type: "string",
|
||||||
|
choices: ["bedrock", "java", "pocketmine", "spigot", "dragonfly"],
|
||||||
|
default: "bedrock"
|
||||||
|
}).option("cronBackup", {
|
||||||
alias: "b",
|
alias: "b",
|
||||||
describe: "cron job to backup server maps",
|
describe: "cron job to backup server maps",
|
||||||
type: "string"
|
type: "string"
|
||||||
|
Reference in New Issue
Block a user