Initial for Backup function
This commit is contained in:
196
server.js
196
server.js
@@ -276,13 +276,15 @@ class ScriptExecutionHandler {
|
||||
* @param {WebSocketMessage} message
|
||||
*/
|
||||
async handleMessage(ws, message) {
|
||||
const { action, scriptPath, executionId, input, mode, server, isUpdate, isShell, containerId } = message;
|
||||
const { action, scriptPath, executionId, input, mode, server, isUpdate, isShell, isBackup, containerId, storage, backupStorage } = message;
|
||||
|
||||
switch (action) {
|
||||
case 'start':
|
||||
if (scriptPath && executionId) {
|
||||
if (isUpdate && containerId) {
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server);
|
||||
if (isBackup && containerId && storage) {
|
||||
await this.startBackupExecution(ws, containerId, executionId, storage, mode, server);
|
||||
} else if (isUpdate && containerId) {
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server, backupStorage);
|
||||
} else if (isShell && containerId) {
|
||||
await this.startShellExecution(ws, containerId, executionId, mode, server);
|
||||
} else {
|
||||
@@ -660,6 +662,115 @@ class ScriptExecutionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start backup execution
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {string} storage
|
||||
* @param {string} mode
|
||||
* @param {ServerInfo|null} server
|
||||
*/
|
||||
async startBackupExecution(ws, containerId, executionId, storage, mode = 'local', server = null) {
|
||||
try {
|
||||
// Send start message
|
||||
this.sendMessage(ws, {
|
||||
type: 'start',
|
||||
data: `Starting backup for container ${containerId} to storage ${storage}...`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
if (mode === 'ssh' && server) {
|
||||
await this.startSSHBackupExecution(ws, containerId, executionId, storage, server);
|
||||
} else {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: 'Backup is only supported via SSH',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: `Failed to start backup: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start SSH backup execution
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {string} storage
|
||||
* @param {ServerInfo} server
|
||||
*/
|
||||
async startSSHBackupExecution(ws, containerId, executionId, storage, server) {
|
||||
const sshService = getSSHExecutionService();
|
||||
|
||||
try {
|
||||
const backupCommand = `vzdump ${containerId} --storage ${storage} --mode snapshot`;
|
||||
|
||||
const execution = await sshService.executeCommand(
|
||||
server,
|
||||
backupCommand,
|
||||
/** @param {string} data */
|
||||
(data) => {
|
||||
this.sendMessage(ws, {
|
||||
type: 'output',
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
},
|
||||
/** @param {string} error */
|
||||
(error) => {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: error,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
},
|
||||
/** @param {number} code */
|
||||
(code) => {
|
||||
if (code === 0) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
data: `Backup completed successfully with exit code: ${code}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
} else {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: `Backup failed with exit code: ${code}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
data: `Backup execution ended with exit code: ${code}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
this.activeExecutions.delete(executionId);
|
||||
}
|
||||
);
|
||||
|
||||
// Store the execution
|
||||
this.activeExecutions.set(executionId, {
|
||||
process: /** @type {any} */ (execution).process,
|
||||
ws
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: `SSH backup execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start update execution (pct enter + update command)
|
||||
* @param {ExtendedWebSocket} ws
|
||||
@@ -667,11 +778,86 @@ class ScriptExecutionHandler {
|
||||
* @param {string} executionId
|
||||
* @param {string} mode
|
||||
* @param {ServerInfo|null} server
|
||||
* @param {string} [backupStorage] - Optional storage to backup to before update
|
||||
*/
|
||||
async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = null) {
|
||||
async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = null, backupStorage = null) {
|
||||
try {
|
||||
// If backup storage is provided, run backup first
|
||||
if (backupStorage && mode === 'ssh' && server) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'start',
|
||||
data: `Starting backup before update for container ${containerId}...`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Create a separate execution ID for backup
|
||||
const backupExecutionId = `backup_${executionId}`;
|
||||
let backupCompleted = false;
|
||||
let backupSucceeded = false;
|
||||
|
||||
// Run backup and wait for it to complete
|
||||
await new Promise<void>((resolve) => {
|
||||
// Create a wrapper websocket that forwards messages and tracks completion
|
||||
const backupWs = {
|
||||
send: (data) => {
|
||||
try {
|
||||
const message = typeof data === 'string' ? JSON.parse(data) : data;
|
||||
|
||||
// Forward all messages to the main websocket
|
||||
ws.send(JSON.stringify(message));
|
||||
|
||||
// Check for completion
|
||||
if (message.type === 'end') {
|
||||
backupCompleted = true;
|
||||
backupSucceeded = !message.data.includes('failed') && !message.data.includes('exit code:');
|
||||
if (!backupSucceeded) {
|
||||
// Backup failed, but we'll still allow update (per requirement 1b)
|
||||
ws.send(JSON.stringify({
|
||||
type: 'output',
|
||||
data: '\n⚠️ Backup failed, but proceeding with update as requested...\n',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
}
|
||||
resolve();
|
||||
} else if (message.type === 'error' && message.data.includes('Backup failed')) {
|
||||
backupCompleted = true;
|
||||
backupSucceeded = false;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'output',
|
||||
data: '\n⚠️ Backup failed, but proceeding with update as requested...\n',
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
resolve();
|
||||
}
|
||||
} catch (e) {
|
||||
// If parsing fails, just forward the raw data
|
||||
ws.send(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Start backup execution
|
||||
this.startSSHBackupExecution(backupWs, containerId, backupExecutionId, backupStorage, server)
|
||||
.catch((error) => {
|
||||
// Backup failed to start, but allow update to proceed
|
||||
if (!backupCompleted) {
|
||||
backupCompleted = true;
|
||||
backupSucceeded = false;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'output',
|
||||
data: `\n⚠️ Backup error: ${error.message}. Proceeding with update...\n`,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Small delay before starting update
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// Send start message
|
||||
// Send start message for update
|
||||
this.sendMessage(ws, {
|
||||
type: 'start',
|
||||
data: `Starting update for container ${containerId}...`,
|
||||
|
||||
Reference in New Issue
Block a user