diff --git a/backup.js b/backup.js index f65bf07..59cfded 100644 --- a/backup.js +++ b/backup.js @@ -1,7 +1,8 @@ require('dotenv').config(); -const { - createDatabaseBackup, - downloadBackupFile, +const { + createDatabaseBackup, + downloadBackupFile, + downloadBackupFileSMB, sendTelegramBroadcast, formatBytes, config: sqlConfig @@ -26,8 +27,18 @@ async function createAndDownloadBackup() { // Step 2: Download the backup file to local folder console.log('Step 2: Downloading backup file to local folder...'); - const localBackupPath = await downloadBackupFile(); - console.log(`Backup downloaded to: ${localBackupPath}`); + + // Try SCP first, fall back to SMB if it fails + let localBackupPath; + try { + localBackupPath = await downloadBackupFile(); + console.log(`Backup downloaded via SCP to: ${localBackupPath}`); + } catch (scpError) { + console.log('SCP download failed, falling back to SMB...'); + console.log(`SCP error: ${scpError.message}`); + localBackupPath = await downloadBackupFileSMB(); + console.log(`Backup downloaded via SMB to: ${localBackupPath}`); + } console.log(''); // Step 3: Check local file and show details diff --git a/do.sh b/do.sh new file mode 100755 index 0000000..fdc1341 --- /dev/null +++ b/do.sh @@ -0,0 +1,2 @@ +node upload.js --port 1433 --sql-path /sharemnt/ez.bak + diff --git a/index.js b/index.js index 85157cb..3027614 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ const path = require('path'); const zlib = require('zlib'); const { pipeline } = require('stream/promises'); const SambaClient = require('samba-client'); +const { Client } = require('ssh2'); // AWS S3 Configuration (v3 client) const s3 = new S3Client({ @@ -36,7 +37,15 @@ const config = { // Backup file path const backupFilePath = process.env.BACKUP_FILE_PATH; -// SMB Configuration +// SCP Configuration (SSH key authentication) +const scpConfig = { + host: process.env.SCP_HOST, + port: parseInt(process.env.SCP_PORT) || 22, + username: process.env.SCP_USERNAME, + privateKey: fs.readFileSync(require('path').resolve(process.env.SCP_KEY_PATH)) +}; + +// Legacy SMB Configuration (deprecated) const smbConfig = { address: process.env.SMB_ADDRESS, username: process.env.SMB_USERNAME, @@ -45,8 +54,8 @@ const smbConfig = { }; // Ensure download directory exists -const downloadFile = process.env.SMB_DOWNLOAD_FILE; -const localDownloadFile = process.env.SMB_LOCAL_DOWNLOAD_FILE; +const downloadFile = process.env.SCP_REMOTE_PATH || process.env.SMB_DOWNLOAD_FILE; +const localDownloadFile = process.env.SCP_LOCAL_PATH || process.env.SMB_LOCAL_DOWNLOAD_FILE; // Admin Telegram Broadcast (env-configured) @@ -153,19 +162,83 @@ async function createDatabaseBackup() { } } -// Function to download backup file from SMB share +// Function to download backup file via SCP async function downloadBackupFile() { + try { + console.log('Downloading backup file via SCP...'); + + const remotePath = process.env.SCP_REMOTE_PATH || '/sharemnt/ez.bak'; + const localPath = process.env.SCP_LOCAL_PATH || 'ez.bak'; + + console.log(`From: ${scpConfig.username}@${scpConfig.host}:${remotePath}`); + console.log(`To: ${localPath}`); + + return new Promise((resolve, reject) => { + const conn = new Client(); + + conn.on('ready', () => { + console.log('SSH connection established'); + + conn.sftp((err, sftp) => { + if (err) { + console.error('SFTP error:', err); + conn.end(); + reject(err); + return; + } + + const readStream = sftp.createReadStream(remotePath); + const writeStream = fs.createWriteStream(localPath); + + readStream.on('error', (err) => { + console.error('Read stream error:', err); + conn.end(); + reject(err); + }); + + writeStream.on('error', (err) => { + console.error('Write stream error:', err); + conn.end(); + reject(err); + }); + + writeStream.on('finish', () => { + console.log('Backup file downloaded successfully to:', localPath); + conn.end(); + resolve(localPath); + }); + + readStream.pipe(writeStream); + }); + }); + + conn.on('error', (err) => { + console.error('SSH connection error:', err); + reject(err); + }); + + conn.connect(scpConfig); + }); + + } catch (err) { + console.error('Error downloading backup file via SCP:', err); + throw err; + } +} + +// Legacy function to download backup file from SMB share (deprecated) +async function downloadBackupFileSMB() { try { console.log('Downloading backup file from SMB share...'); - + // Create SMB client const client = new SambaClient(smbConfig); - + // Download file from SMB share - await client.getFile(downloadFile, localDownloadFile); - - console.log('Backup file downloaded successfully to:', localDownloadFile); - return localDownloadFile; + await client.getFile(process.env.SMB_DOWNLOAD_FILE, process.env.SMB_LOCAL_DOWNLOAD_FILE); + + console.log('Backup file downloaded successfully to:', process.env.SMB_LOCAL_DOWNLOAD_FILE); + return process.env.SMB_LOCAL_DOWNLOAD_FILE; } catch (err) { console.error('Error downloading backup file from SMB share:', err); throw err; @@ -358,12 +431,14 @@ if (require.main === module) { module.exports = { createDatabaseBackup, downloadBackupFile, + downloadBackupFileSMB, // Legacy SMB function compressBackupFile, formatBytes, sendTelegramBroadcast, getDbSizeBytes, // Export configurations that might be needed - smbConfig, + scpConfig, + smbConfig, // Legacy SMB config config: config, // MSSQL config s3 // S3 client }; diff --git a/package-lock.json b/package-lock.json index 6258c56..3fecc2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@aws-sdk/client-s3": "^3.859.0", "dotenv": "^16.0.3", "mssql": "^9.1.1", - "samba-client": "^7.2.0" + "samba-client": "^7.2.0", + "ssh2": "^1.15.0" }, "devDependencies": { "nodemon": "^3.0.1" @@ -2104,6 +2105,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2155,6 +2165,15 @@ ], "license": "MIT" }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2259,6 +2278,15 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2347,6 +2375,20 @@ "dev": true, "license": "MIT" }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3643,6 +3685,13 @@ "node": ">=10" } }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT", + "optional": true + }, "node_modules/native-duplexpair": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", @@ -4196,6 +4245,23 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, + "node_modules/ssh2": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.23.0" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -4379,6 +4445,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", diff --git a/package.json b/package.json index a1bfb05..0f84ba7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "@aws-sdk/client-s3": "^3.859.0", "dotenv": "^16.0.3", "mssql": "^9.1.1", - "samba-client": "^7.2.0" + "samba-client": "^7.2.0", + "ssh2": "^1.15.0" }, "devDependencies": { "nodemon": "^3.0.1" diff --git a/test-scp.js b/test-scp.js new file mode 100644 index 0000000..04d9ad5 --- /dev/null +++ b/test-scp.js @@ -0,0 +1,72 @@ +require('dotenv').config(); +const { Client } = require('ssh2'); + +// Test SCP connection +async function testSCPConnection() { + const config = { + host: process.env.SCP_HOST, + port: parseInt(process.env.SCP_PORT) || 22, + username: process.env.SCP_USERNAME, + privateKey: require('fs').readFileSync(require('path').resolve(process.env.SCP_KEY_PATH)) + }; + + console.log('Testing SCP connection...'); + console.log(`Host: ${config.host}`); + console.log(`Port: ${config.port}`); + console.log(`Username: ${config.username}`); + + const conn = new Client(); + + return new Promise((resolve, reject) => { + conn.on('ready', () => { + console.log('✅ SSH connection successful!'); + console.log('Now testing SCP download...'); + + conn.sftp((err, sftp) => { + if (err) { + console.error('❌ SFTP error:', err.message); + conn.end(); + reject(err); + return; + } + + const remotePath = process.env.SCP_REMOTE_PATH || '/sharemnt/ez.bak'; + console.log(`Checking remote file: ${remotePath}`); + + sftp.stat(remotePath, (err, stats) => { + if (err) { + console.error('❌ Cannot access remote file:', err.message); + conn.end(); + reject(err); + } else { + console.log('✅ Remote file found!'); + console.log(`File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); + console.log(`Modified: ${stats.mtime}`); + conn.end(); + resolve(); + } + }); + }); + }); + + conn.on('error', (err) => { + console.error('❌ SSH connection failed:', err.message); + console.error('Error details:', err); + reject(err); + }); + + conn.connect(config); + }); +} + +// Run test +testSCPConnection() + .then(() => { + console.log('\n🎉 SCP test completed successfully!'); + console.log('You can now use: node backup.js'); + }) + .catch((err) => { + console.error('\n💥 SCP test failed!'); + console.error('Please check your SSH configuration.'); + process.exit(1); + });