Compare commits

...

3 Commits

Author SHA1 Message Date
sebseb7
27e1354129 Fix: Close existing SQL connections before backup to prevent abnormal termination
- Added explicit connection cleanup in createDatabaseBackup()
- Resolves 'BACKUP DATABASE is terminating abnormally' error in runBackupProcess()
- Prevents connection pool conflicts when running scheduled backups
2025-10-02 15:13:56 +02:00
sebseb7
307a373667 u 2025-09-16 09:05:51 +02:00
sebseb7
48f1fae32e x 2025-09-16 09:02:58 +02:00
5 changed files with 189 additions and 19 deletions

View File

@@ -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

2
do.sh Executable file
View File

@@ -0,0 +1,2 @@
node upload.js --port 1433 --sql-path /sharemnt/ez.bak

108
index.js
View File

@@ -4,9 +4,11 @@ const sql = require('mssql');
const { S3Client, PutObjectCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3');
const fs = require('fs');
const path = require('path');
const os = require('os');
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 +38,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(path.resolve(process.env.SCP_KEY_PATH.replace(/^~/, os.homedir())))
};
// Legacy SMB Configuration (deprecated)
const smbConfig = {
address: process.env.SMB_ADDRESS,
username: process.env.SMB_USERNAME,
@@ -45,8 +55,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)
@@ -134,13 +144,21 @@ async function getDbSizeBytes() {
async function createDatabaseBackup() {
try {
console.log('Connecting to database...');
// Close any existing connection to avoid conflicts
try {
await sql.close();
} catch (closeErr) {
// Ignore error if no connection exists
}
await sql.connect(config);
console.log('Creating database backup...');
const backupQuery = `
BACKUP DATABASE [${process.env.MSSQL_DATABASE}]
TO DISK = N'${backupFilePath}'
WITH FORMAT, INIT, NAME = N'${process.env.MSSQL_DATABASE}-Vollständig Datenbank Sichern',
WITH FORMAT, INIT, COMPRESSION, NAME = N'${process.env.MSSQL_DATABASE}-Vollständig Datenbank Sichern',
SKIP, NOREWIND, NOUNLOAD, STATS = 10
`;
@@ -153,19 +171,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 +440,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
};

74
package-lock.json generated
View File

@@ -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",

View File

@@ -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"