const fsPromises = require('fs').promises; // Ensure you use the promise-based API const fs = require('fs'); const path = require('path'); const { decryptFileInPlace, encryptFileInPlace, encryptFileWithKey, decryptFileWithKey} = require("../src/main/aes_encrypt"); const {lockFile, unlockFile} = require("../helpers/lock_mechanism"); async function decryptAndGetServerIP(filePath) { try { await lockFile(filePath); await decryptFileInPlace(filePath); const fileContent = await fs.promises.readFile(filePath, 'utf8'); const jsonData = JSON.parse(fileContent); const serverIP = jsonData.ip; await encryptFileInPlace(filePath) await unlockFile(filePath); return serverIP; } catch (error) { console.error('An error occurred:', error); return null; } } function extractFilePaths(directoryStructure) { let paths = []; const traverse = (dir, currentPath) => { Object.keys(dir).forEach(key => { if (key === 'files') { dir[key].forEach(file => paths.push(path.join(currentPath, file))); } else { traverse(dir[key], path.join(currentPath, key)); } }); }; traverse(directoryStructure, ''); return paths; } async function fetchFiles(ip, filePaths) { let results = {}; for (const filePath of filePaths) { const url = `http://${ip}:3000/file_path`; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filePath }) }); if (!response.ok) throw new Error(`HTTP status ${response.status}`); const arrayBuffer = await response.arrayBuffer(); // Fetch the response as an ArrayBuffer results[filePath] = Buffer.from(arrayBuffer); } catch (error) { console.error(`Error fetching ${filePath}:`, error); results[filePath] = null; } } return results; } async function removeDirectory(directoryPath) { try { // Check if the directory exists const stats = await fsPromises.stat(directoryPath); if (!stats.isDirectory()) { console.log('The specified path is not a directory.'); return; } // Read all the contents of the directory const files = await fsPromises.readdir(directoryPath); // Loop through each file/directory and delete them for (const file of files) { const currentPath = path.join(directoryPath, file); const currentStats = await fsPromises.stat(currentPath); if (currentStats.isDirectory()) { // Recursive call for directories await removeDirectory(currentPath); } else { await lockFile(currentPath); await unlockFile(currentPath); await fsPromises.unlink(currentPath); } } // Finally, delete the directory itself await fsPromises.rmdir(directoryPath); //console.log(`Directory removed: ${directoryPath}`); } catch (error) { console.error(`Error removing directory: ${error.message}`); } } async function decryptUserSystemConfig() { const configPath = path.join(__dirname, '..', 'usersInSystem.json'); await lockFile(configPath); await decryptFileInPlace(configPath); } async function encryptUserSystemConfig(){ const configPath = path.join(__dirname, '..', 'usersInSystem.json'); await encryptFileInPlace(configPath); await unlockFile(configPath); } async function processBackup() { const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json'); const backupConfigPath = path.join(__dirname, '..', 'backupSchemes.json'); const baseBackupDirPath = path.join(__dirname, '..', 'backup_directories'); if(!fs.existsSync(backupConfigPath) || !fs.existsSync(usersConfigPath)){ console.log('Can\'t start backup system: necessary files missing!'); return; } await decryptUserSystemConfig(); try { // Read and parse the users configuration const usersData = await fsPromises.readFile(usersConfigPath, 'utf8'); const usersConfig = JSON.parse(usersData); // Read and parse the backup configuration const backupData = await fsPromises.readFile(backupConfigPath, 'utf8'); const jsonData = JSON.parse(backupData); for (const key in jsonData) { const userBackupDir = path.join(baseBackupDirPath, key); await removeDirectory(userBackupDir); const node = jsonData[key]; const directoryStructure = JSON.parse(node.directoryStructure); const filePaths = extractFilePaths(directoryStructure); const files = await fetchFiles(node.ip, filePaths); await fsPromises.mkdir(userBackupDir, { recursive: true }); // Find the encryption key for the user's department let encryptionKey = ''; for (const dept in usersConfig) { if (usersConfig[dept].users.includes(key)) { encryptionKey = usersConfig[dept].key; break; } } for (const file in files) { if (files[file]) { const fullPath = path.join(userBackupDir, file); await fsPromises.mkdir(path.dirname(fullPath), { recursive: true }); await fsPromises.writeFile(fullPath, files[file]); // Write the binary data // Encrypt the file in place after writing it if (encryptionKey) { await encryptFileWithKey(fullPath, encryptionKey); } } } } } catch (error) { console.error('Failed to read or process the JSON file:', error); } await encryptUserSystemConfig(); } async function getAllFilePaths(dirPath) { let filePaths = []; async function recurse(currentPath) { const entries = await fsPromises.readdir(currentPath, { withFileTypes: true }); // Create promises for each entry and process them in parallel const entryPromises = entries.map(async (entry) => { const resolvedPath = path.join(currentPath, entry.name); if (entry.isDirectory()) { // If it's a directory, recurse into it await recurse(resolvedPath); } else { // If it's a file, add it to the file paths array filePaths.push(resolvedPath); } }); // Wait for all promises to complete await Promise.all(entryPromises); } await recurse(dirPath); return filePaths; } async function decryptBackupFilesToDirectory(destinationDir) { await decryptUserSystemConfig(); const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json'); if(!fs.existsSync(usersConfigPath)){ return false; } const usersConfig = JSON.parse(await fsPromises.readFile(usersConfigPath, 'utf8')); const sourceDir = path.join(__dirname, '..', 'backup_directories'); await removeDirectory(destinationDir); try { for (const department in usersConfig) { const { key, users } = usersConfig[department]; for (const userId of users) { const userDir = path.join(sourceDir, userId); try { const files = await getAllFilePaths(userDir); for (const filePath of files) { const stats = await fsPromises.stat(filePath); if (stats.isFile()) { const relativePath = path.relative(userDir, filePath); // Construct the destination file path const destinationFilePath = path.join(destinationDir, userId, relativePath); await fsPromises.mkdir(path.dirname(destinationFilePath), { recursive: true }); await lockFile(filePath); // Decrypt the file in its original location await decryptFileWithKey(filePath, key); // Copy the decrypted file to the destination directory await fsPromises.copyFile(filePath, destinationFilePath); console.log(`Decrypted file copied to: ${destinationFilePath}`); // Re-encrypt the file in its original location (optional) await encryptFileWithKey(filePath, key); await unlockFile(filePath); } } } catch (error) { console.error(`Error processing files for user ${userId}: ${error.message}`); await encryptUserSystemConfig(); return false; } } } } catch (error) { console.error(`Error processing decryption: ${error.message}`); await encryptUserSystemConfig(); return false; } await encryptUserSystemConfig(); return true; } async function decryptDepartmentFilesToDirectory() { const dirDepartmentPath = path.join(__dirname, '..', 'dirDepartment.json'); const loginDataPath = path.join(__dirname, '..', 'loginData.json'); const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json'); const sourceDir = path.join(__dirname, '..', 'backup_directories'); if (!fs.existsSync(usersConfigPath) || !fs.existsSync(loginDataPath) || !fs.existsSync(dirDepartmentPath)) { console.error("Missing one or more essential configuration files."); return false; } await lockFile(loginDataPath); await lockFile(usersConfigPath); await decryptFileInPlace(loginDataPath); await decryptFileInPlace(usersConfigPath); const { path: destinationDir } = JSON.parse(await fsPromises.readFile(dirDepartmentPath, 'utf8')); const { department } = JSON.parse(await fsPromises.readFile(loginDataPath, 'utf8')); const usersConfig = JSON.parse(await fsPromises.readFile(usersConfigPath, 'utf8')); // Check if department exists in users configuration if (!usersConfig[department]) { console.error(`No configuration found for department: ${department}`); return false; } const { key, users } = usersConfig[department]; await encryptFileInPlace(loginDataPath); await encryptFileInPlace(usersConfigPath); await unlockFile(loginDataPath); await unlockFile(usersConfigPath); await removeDirectory(destinationDir); for (const userId of users) { const userDir = path.join(sourceDir, userId); try { const files = await getAllFilePaths(userDir); console.log(`Processing files for user ${userId}: ${files}`); for (const filePath of files) { const stats = await fsPromises.stat(filePath); if (stats.isFile()) { const relativePath = path.relative(userDir, filePath); // Construct the destination file path const destinationFilePath = path.join(destinationDir, userId, relativePath); await fsPromises.mkdir(path.dirname(destinationFilePath), { recursive: true }); // Decrypt the file in its original location await decryptFileWithKey(filePath, key); // Copy the decrypted file to the destination directory await fsPromises.copyFile(filePath, destinationFilePath); console.log(`Decrypted file copied to: ${destinationFilePath}`); // Re-encrypt the file in its original location (optional) await encryptFileWithKey(filePath, key); } } } catch (error) { console.error(`Error processing files for user ${userId}: ${error.message}`); return false; } } console.log("All files processed successfully."); return true; } let serverIp = null; decryptAndGetServerIP(path.join(__dirname, '..', 'ipConfig.json')).then(ip => { serverIp = ip; }); const timeoutInterval = 7 * 60 * 1000 //minutes * seconds * miliseconds setInterval(async () => { await decryptDepartmentFilesToDirectory(); }, timeoutInterval); console.log('backup.js process started!'); process.on('SIGINT', async () => { console.log('Shutdown signal received in \'backup.js\'. Cleaning up...'); try { console.log('Performing cleanup tasks...'); console.log('Cleanup completed successfully.'); } catch (error) { console.error('An error occurred during cleanup:', error); } finally { console.log('Process terminated'); process.exit(0); } }); process.on('message', async (message) => { if (message.type === 'startBackup') { console.log('BackupProcess: Received command to start backup.'); await processBackup() } if(message.type === 'decryptBackup'){ console.log(`BackupProcess: Received command to decrypt backup to ${message.decryptDestPath}`); console.log(message.decryptDestPath); await decryptBackupFilesToDirectory(message.decryptDestPath); } });