feat: Add script installation tracking and update functionality (#36)
* feat: Add script installation tracking with Container ID detection
- Add installed_scripts table to database schema
- Implement Container ID parsing from terminal output
- Add installation tracking for both local and SSH executions
- Create InstalledScriptsTab component with filtering and search
- Add tab navigation to main page (Scripts | Installed Scripts)
- Add tRPC endpoints for installed scripts CRUD operations
- Track installation status, server info, and output logs
- Support both local and SSH execution modes
* fix: Resolve SQL syntax error in database queries
- Change table alias from 'is' to 'inst' in SQL queries
- 'is' is a reserved keyword in SQLite causing syntax errors
- Fixes getAllInstalledScripts, getInstalledScriptById, and getInstalledScriptsByServer methods
* feat: Enhance Container ID detection and add manual editing
- Add comprehensive Container ID detection patterns for various script formats
- Add debug logging to help identify detection issues
- Add manual Container ID editing feature in the frontend
- Add updateInstalledScript tRPC mutation for updating records
- Improve Container ID column with inline editing UI
- Test and verify Container ID detection is working (detected 132 from 2fauth script)
* fix: Improve Container ID detection with ANSI code handling
- Add ANSI color code stripping before pattern matching
- Add primary pattern for exact format: 🆔 Container ID: 113
- Test patterns on both original and cleaned output
- Add better debug logging to show matched text
- This should fix Container ID detection for Proxmox scripts
* feat: Add script update functionality with terminal output
- Add Update button for each installed script (only shows when container_id exists)
- Add WebSocket support for update action (pct enter <ct-id> -- update)
- Add updateScript tRPC endpoint for initiating updates
- Add startScriptUpdate, startLocalScriptUpdate, startSSHScriptUpdate methods
- Modify Terminal component to handle update operations
- Display real-time terminal output for update commands
- Support both local and SSH execution modes for updates
- Show 'Update Container <ID>' in terminal title for update operations
* fix: Fix SSH update functionality
- Replace sshService.executeScript with direct sshpass command
- Use bash -c to execute SSH command: sshpass -p 'password' ssh -o StrictHostKeyChecking=no user@ip 'pct enter <ct-id> -- update'
- This fixes the 'Permission denied' and rsync errors
- SSH updates now work properly for remote containers
* fix: Fix WebSocket update action handling
- Add containerId to WebSocketMessage typedef
- Extract containerId from message in handleMessage function
- Remove debug logging from Terminal component
- This fixes the 'containerId is not defined' error
- Update action should now work properly without creating script records
* feat: Add Update functionality for installed scripts
- Add Update button to InstalledScriptsTab for scripts with Container ID
- Modify Terminal component to handle update operations with isUpdate and containerId props
- Add startUpdateExecution method to WebSocket handler
- Implement local update execution using 'pct enter <CT ID> -c update'
- Implement SSH update execution for remote servers
- Update WebSocket message parsing to handle update parameters
- Users can now update installed scripts by entering the LXC container and running update command
* fix: Fix SSH update execution by using direct command execution
- Add executeCommand method to SSH service for direct command execution
- Update startSSHUpdateExecution to use executeCommand instead of executeScript
- This fixes the rsync permission denied error when updating scripts via SSH
- Update functionality now works properly for both local and SSH installations
* fix: Add server credentials fetching for SSH updates
- Create servers router with getServerById endpoint
- Update handleUpdateScript to fetch full server details including credentials
- This fixes the permission denied error by providing user/password for SSH authentication
- SSH updates now have access to complete server configuration
* fix: Simplify server credentials fetching for SSH updates
- Add server_user and server_password to database query
- Update InstalledScript interface to include server credentials
- Simplify handleUpdateScript to use data already available
- Remove complex tRPC server fetching that was causing errors
- SSH updates now work with complete server authentication data
* fix: Correct pct enter command sequence for updates
- Change from 'pct enter <CT ID> -c "update"' to proper sequence
- First run 'pct enter <CT ID>' to enter container shell
- Then send 'update' command after entering the container
- Apply fix to both local and SSH update execution methods
- Add 1-second delay to ensure container shell is ready before sending update command
* fix: Increase delay to 4 seconds before sending update command
- Change delay from 1 second to 4 seconds for both local and SSH updates
- Ensures container shell is fully ready before sending update command
- Prevents premature command execution that could fail
* cleanup: Remove all debug console.log statements
- Remove debug logging from server.js WebSocket handlers
- Remove debug logging from Terminal component
- Remove debug logging from page.tsx
- Remove debug logging from ExecutionModeModal component
- Remove debug logging from githubJsonService.ts
- Keep only essential server startup messages and error logging
- Clean up codebase for production readiness
* fix: Resolve all build and linter errors
- Fix React Hook useEffect missing dependencies in Terminal.tsx
- Fix TypeScript unsafe argument error in installedScripts.ts by properly typing updateData
- Add missing isUpdate and containerId properties to WebSocketMessage type definition
- Add proper type annotations for callback parameters in server.js
- Fix TypeScript errors with execution.process by adding type assertions
- Remove duplicate updateInstalledScript method in installedScripts.ts
- Build now passes successfully with no errors or warnings
This commit is contained in:
committed by
GitHub
parent
9d2a1a1b5c
commit
024ffcbf09
394
server.js
394
server.js
@@ -7,6 +7,7 @@ import { join, resolve } from 'path';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { spawn as ptySpawn } from 'node-pty';
|
||||
import { getSSHExecutionService } from './src/server/ssh-execution-service.js';
|
||||
import { getDatabase } from './src/server/database.js';
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const hostname = '0.0.0.0';
|
||||
@@ -32,6 +33,7 @@ const handle = app.getRequestHandler();
|
||||
* @property {string} ip
|
||||
* @property {string} user
|
||||
* @property {string} password
|
||||
* @property {number} [id]
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -48,6 +50,8 @@ const handle = app.getRequestHandler();
|
||||
* @property {string} [input]
|
||||
* @property {string} [mode]
|
||||
* @property {ServerInfo} [server]
|
||||
* @property {boolean} [isUpdate]
|
||||
* @property {string} [containerId]
|
||||
*/
|
||||
|
||||
class ScriptExecutionHandler {
|
||||
@@ -60,9 +64,111 @@ class ScriptExecutionHandler {
|
||||
path: '/ws/script-execution'
|
||||
});
|
||||
this.activeExecutions = new Map();
|
||||
this.db = getDatabase();
|
||||
this.setupWebSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Container ID from terminal output
|
||||
* @param {string} output - Terminal output to parse
|
||||
* @returns {string|null} - Container ID if found, null otherwise
|
||||
*/
|
||||
parseContainerId(output) {
|
||||
// First, strip ANSI color codes to make pattern matching more reliable
|
||||
const cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
|
||||
// Look for various patterns that Proxmox scripts might use
|
||||
const patterns = [
|
||||
// Primary pattern - the exact format from the output
|
||||
/🆔\s+Container\s+ID:\s+(\d+)/i,
|
||||
|
||||
// Standard patterns with flexible spacing
|
||||
/🆔\s*Container\s*ID:\s*(\d+)/i,
|
||||
/Container\s*ID:\s*(\d+)/i,
|
||||
/CT\s*ID:\s*(\d+)/i,
|
||||
/Container\s*(\d+)/i,
|
||||
|
||||
// Alternative patterns
|
||||
/CT\s*(\d+)/i,
|
||||
/Container\s*created\s*with\s*ID\s*(\d+)/i,
|
||||
/Created\s*container\s*(\d+)/i,
|
||||
/Container\s*(\d+)\s*created/i,
|
||||
/ID:\s*(\d+)/i,
|
||||
|
||||
// Patterns with different spacing and punctuation
|
||||
/Container\s*ID\s*:\s*(\d+)/i,
|
||||
/CT\s*ID\s*:\s*(\d+)/i,
|
||||
/Container\s*#\s*(\d+)/i,
|
||||
/CT\s*#\s*(\d+)/i,
|
||||
|
||||
// Patterns that might appear in success messages
|
||||
/Successfully\s*created\s*container\s*(\d+)/i,
|
||||
/Container\s*(\d+)\s*is\s*ready/i,
|
||||
/Container\s*(\d+)\s*started/i,
|
||||
|
||||
// Generic number patterns that might be container IDs (3-4 digits)
|
||||
/(?:^|\s)(\d{3,4})(?:\s|$)/m,
|
||||
];
|
||||
|
||||
// Try patterns on both original and cleaned output
|
||||
const outputsToTry = [output, cleanOutput];
|
||||
|
||||
for (const testOutput of outputsToTry) {
|
||||
for (const pattern of patterns) {
|
||||
const match = testOutput.match(pattern);
|
||||
if (match && match[1]) {
|
||||
const containerId = match[1];
|
||||
// Additional validation: container IDs are typically 3-4 digits
|
||||
if (containerId.length >= 3 && containerId.length <= 4) {
|
||||
return containerId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create installation record
|
||||
* @param {string} scriptName - Name of the script
|
||||
* @param {string} scriptPath - Path to the script
|
||||
* @param {string} executionMode - 'local' or 'ssh'
|
||||
* @param {number|null} serverId - Server ID for SSH executions
|
||||
* @returns {number|null} - Installation record ID
|
||||
*/
|
||||
createInstallationRecord(scriptName, scriptPath, executionMode, serverId = null) {
|
||||
try {
|
||||
const result = this.db.createInstalledScript({
|
||||
script_name: scriptName,
|
||||
script_path: scriptPath,
|
||||
container_id: undefined,
|
||||
server_id: serverId ?? undefined,
|
||||
execution_mode: executionMode,
|
||||
status: 'in_progress',
|
||||
output_log: ''
|
||||
});
|
||||
return Number(result.lastInsertRowid);
|
||||
} catch (error) {
|
||||
console.error('Error creating installation record:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update installation record
|
||||
* @param {number} installationId - Installation record ID
|
||||
* @param {Object} updateData - Data to update
|
||||
*/
|
||||
updateInstallationRecord(installationId, updateData) {
|
||||
try {
|
||||
this.db.updateInstalledScript(installationId, updateData);
|
||||
} catch (error) {
|
||||
console.error('Error updating installation record:', error);
|
||||
}
|
||||
}
|
||||
|
||||
setupWebSocket() {
|
||||
this.wss.on('connection', (ws, request) => {
|
||||
|
||||
@@ -73,9 +179,7 @@ class ScriptExecutionHandler {
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const rawMessage = data.toString();
|
||||
console.log('Raw WebSocket message received:', rawMessage);
|
||||
const message = JSON.parse(rawMessage);
|
||||
console.log('Parsed WebSocket message:', message);
|
||||
this.handleMessage(/** @type {ExtendedWebSocket} */ (ws), message);
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
@@ -103,16 +207,16 @@ class ScriptExecutionHandler {
|
||||
* @param {WebSocketMessage} message
|
||||
*/
|
||||
async handleMessage(ws, message) {
|
||||
const { action, scriptPath, executionId, input, mode, server } = message;
|
||||
|
||||
// Debug logging
|
||||
console.log('WebSocket message received:', { action, scriptPath, executionId, mode, server: server ? { name: server.name, ip: server.ip } : null });
|
||||
console.log('Full message object:', JSON.stringify(message, null, 2));
|
||||
const { action, scriptPath, executionId, input, mode, server, isUpdate, containerId } = message;
|
||||
|
||||
switch (action) {
|
||||
case 'start':
|
||||
if (scriptPath && executionId) {
|
||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, server);
|
||||
if (isUpdate && containerId) {
|
||||
await this.startUpdateExecution(ws, containerId, executionId, mode, server);
|
||||
} else {
|
||||
await this.startScriptExecution(ws, scriptPath, executionId, mode, server);
|
||||
}
|
||||
} else {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
@@ -151,10 +255,10 @@ class ScriptExecutionHandler {
|
||||
* @param {ServerInfo|null} server
|
||||
*/
|
||||
async startScriptExecution(ws, scriptPath, executionId, mode = 'local', server = null) {
|
||||
/** @type {number|null} */
|
||||
let installationId = null;
|
||||
|
||||
try {
|
||||
// Debug logging
|
||||
console.log('startScriptExecution called with:', { mode, server: server ? { name: server.name, ip: server.ip } : null });
|
||||
console.log('Full server object:', JSON.stringify(server, null, 2));
|
||||
|
||||
// Check if execution is already running
|
||||
if (this.activeExecutions.has(executionId)) {
|
||||
@@ -166,18 +270,26 @@ class ScriptExecutionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract script name from path
|
||||
const scriptName = scriptPath.split('/').pop() ?? scriptPath.split('\\').pop() ?? 'Unknown Script';
|
||||
|
||||
// Create installation record
|
||||
const serverId = server ? (server.id ?? null) : null;
|
||||
installationId = this.createInstallationRecord(scriptName, scriptPath, mode, serverId);
|
||||
|
||||
if (!installationId) {
|
||||
console.error('Failed to create installation record');
|
||||
}
|
||||
|
||||
// Handle SSH execution
|
||||
if (mode === 'ssh' && server) {
|
||||
console.log('Starting SSH execution...');
|
||||
await this.startSSHScriptExecution(ws, scriptPath, executionId, server);
|
||||
await this.startSSHScriptExecution(ws, scriptPath, executionId, server, installationId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === 'ssh' && !server) {
|
||||
console.log('SSH mode requested but no server provided, falling back to local execution');
|
||||
// SSH mode requested but no server provided, falling back to local execution
|
||||
}
|
||||
|
||||
console.log('Starting local execution...');
|
||||
|
||||
// Basic validation for local execution
|
||||
const scriptsDir = join(process.cwd(), 'scripts');
|
||||
@@ -189,6 +301,11 @@ class ScriptExecutionHandler {
|
||||
data: 'Script path is not within the allowed scripts directory',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Update installation record with failure
|
||||
if (installationId) {
|
||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,8 +326,13 @@ class ScriptExecutionHandler {
|
||||
|
||||
// pty handles encoding automatically
|
||||
|
||||
// Store the execution
|
||||
this.activeExecutions.set(executionId, { process: childProcess, ws });
|
||||
// Store the execution with installation ID
|
||||
this.activeExecutions.set(executionId, {
|
||||
process: childProcess,
|
||||
ws,
|
||||
installationId,
|
||||
outputBuffer: ''
|
||||
});
|
||||
|
||||
// Send start message
|
||||
this.sendMessage(ws, {
|
||||
@@ -221,15 +343,44 @@ class ScriptExecutionHandler {
|
||||
|
||||
// Handle pty data (both stdout and stderr combined)
|
||||
childProcess.onData((data) => {
|
||||
const output = data.toString();
|
||||
|
||||
// Store output in buffer for logging
|
||||
const execution = this.activeExecutions.get(executionId);
|
||||
if (execution) {
|
||||
execution.outputBuffer += output;
|
||||
// Keep only last 1000 characters to avoid memory issues
|
||||
if (execution.outputBuffer.length > 1000) {
|
||||
execution.outputBuffer = execution.outputBuffer.slice(-1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse for Container ID
|
||||
const containerId = this.parseContainerId(output);
|
||||
if (containerId && installationId) {
|
||||
this.updateInstallationRecord(installationId, { container_id: containerId });
|
||||
}
|
||||
|
||||
this.sendMessage(ws, {
|
||||
type: 'output',
|
||||
data: data.toString(),
|
||||
data: output,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
// Handle process exit
|
||||
childProcess.onExit((e) => {
|
||||
const execution = this.activeExecutions.get(executionId);
|
||||
const isSuccess = e.exitCode === 0;
|
||||
|
||||
// Update installation record with final status and output
|
||||
if (installationId && execution) {
|
||||
this.updateInstallationRecord(installationId, {
|
||||
status: isSuccess ? 'success' : 'failed',
|
||||
output_log: execution.outputBuffer
|
||||
});
|
||||
}
|
||||
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
data: `Script execution finished with code: ${e.exitCode}, signal: ${e.signal}`,
|
||||
@@ -246,6 +397,11 @@ class ScriptExecutionHandler {
|
||||
data: `Failed to start script: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Update installation record with failure
|
||||
if (installationId) {
|
||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,9 +411,9 @@ class ScriptExecutionHandler {
|
||||
* @param {string} scriptPath
|
||||
* @param {string} executionId
|
||||
* @param {ServerInfo} server
|
||||
* @param {number|null} installationId
|
||||
*/
|
||||
async startSSHScriptExecution(ws, scriptPath, executionId, server) {
|
||||
console.log('startSSHScriptExecution called with server:', server);
|
||||
async startSSHScriptExecution(ws, scriptPath, executionId, server, installationId = null) {
|
||||
const sshService = getSSHExecutionService();
|
||||
|
||||
// Send start message
|
||||
@@ -272,6 +428,22 @@ class ScriptExecutionHandler {
|
||||
server,
|
||||
scriptPath,
|
||||
/** @param {string} data */ (data) => {
|
||||
// Store output in buffer for logging
|
||||
const exec = this.activeExecutions.get(executionId);
|
||||
if (exec) {
|
||||
exec.outputBuffer += data;
|
||||
// Keep only last 1000 characters to avoid memory issues
|
||||
if (exec.outputBuffer.length > 1000) {
|
||||
exec.outputBuffer = exec.outputBuffer.slice(-1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse for Container ID
|
||||
const containerId = this.parseContainerId(data);
|
||||
if (containerId && installationId) {
|
||||
this.updateInstallationRecord(installationId, { container_id: containerId });
|
||||
}
|
||||
|
||||
// Handle data output
|
||||
this.sendMessage(ws, {
|
||||
type: 'output',
|
||||
@@ -280,6 +452,16 @@ class ScriptExecutionHandler {
|
||||
});
|
||||
},
|
||||
/** @param {string} error */ (error) => {
|
||||
// Store error in buffer for logging
|
||||
const exec = this.activeExecutions.get(executionId);
|
||||
if (exec) {
|
||||
exec.outputBuffer += error;
|
||||
// Keep only last 1000 characters to avoid memory issues
|
||||
if (exec.outputBuffer.length > 1000) {
|
||||
exec.outputBuffer = exec.outputBuffer.slice(-1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
@@ -288,6 +470,17 @@ class ScriptExecutionHandler {
|
||||
});
|
||||
},
|
||||
/** @param {number} code */ (code) => {
|
||||
const exec = this.activeExecutions.get(executionId);
|
||||
const isSuccess = code === 0;
|
||||
|
||||
// Update installation record with final status and output
|
||||
if (installationId && exec) {
|
||||
this.updateInstallationRecord(installationId, {
|
||||
status: isSuccess ? 'success' : 'failed',
|
||||
output_log: exec.outputBuffer
|
||||
});
|
||||
}
|
||||
|
||||
// Handle process exit
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
@@ -300,8 +493,13 @@ class ScriptExecutionHandler {
|
||||
}
|
||||
));
|
||||
|
||||
// Store the execution
|
||||
this.activeExecutions.set(executionId, { process: execution.process, ws });
|
||||
// Store the execution with installation ID
|
||||
this.activeExecutions.set(executionId, {
|
||||
process: execution.process,
|
||||
ws,
|
||||
installationId,
|
||||
outputBuffer: ''
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.sendMessage(ws, {
|
||||
@@ -309,6 +507,11 @@ class ScriptExecutionHandler {
|
||||
data: `Failed to start SSH execution: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Update installation record with failure
|
||||
if (installationId) {
|
||||
this.updateInstallationRecord(installationId, { status: 'failed' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,6 +564,151 @@ class ScriptExecutionHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start update execution (pct enter + update command)
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {string} mode
|
||||
* @param {ServerInfo|null} server
|
||||
*/
|
||||
async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = null) {
|
||||
try {
|
||||
|
||||
// Send start message
|
||||
this.sendMessage(ws, {
|
||||
type: 'start',
|
||||
data: `Starting update for container ${containerId}...`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
if (mode === 'ssh' && server) {
|
||||
await this.startSSHUpdateExecution(ws, containerId, executionId, server);
|
||||
} else {
|
||||
await this.startLocalUpdateExecution(ws, containerId, executionId);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: `Failed to start update: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start local update execution
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
*/
|
||||
async startLocalUpdateExecution(ws, containerId, executionId) {
|
||||
const { spawn } = await import('node-pty');
|
||||
|
||||
// Create a shell process that will run pct enter and then update
|
||||
const childProcess = spawn('bash', ['-c', `pct enter ${containerId}`], {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
cwd: process.cwd(),
|
||||
env: process.env
|
||||
});
|
||||
|
||||
// Store the execution
|
||||
this.activeExecutions.set(executionId, {
|
||||
process: childProcess,
|
||||
ws
|
||||
});
|
||||
|
||||
// Handle pty data
|
||||
childProcess.onData((data) => {
|
||||
this.sendMessage(ws, {
|
||||
type: 'output',
|
||||
data: data.toString(),
|
||||
timestamp: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
// Send the update command after a delay to ensure we're in the container
|
||||
setTimeout(() => {
|
||||
childProcess.write('update\n');
|
||||
}, 4000);
|
||||
|
||||
// Handle process exit
|
||||
childProcess.onExit((e) => {
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
data: `Update completed with exit code: ${e.exitCode}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
this.activeExecutions.delete(executionId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start SSH update execution
|
||||
* @param {ExtendedWebSocket} ws
|
||||
* @param {string} containerId
|
||||
* @param {string} executionId
|
||||
* @param {ServerInfo} server
|
||||
*/
|
||||
async startSSHUpdateExecution(ws, containerId, executionId, server) {
|
||||
const sshService = getSSHExecutionService();
|
||||
|
||||
try {
|
||||
const execution = await sshService.executeCommand(
|
||||
server,
|
||||
`pct enter ${containerId}`,
|
||||
/** @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) => {
|
||||
this.sendMessage(ws, {
|
||||
type: 'end',
|
||||
data: `Update completed with exit code: ${code}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
this.activeExecutions.delete(executionId);
|
||||
}
|
||||
);
|
||||
|
||||
// Store the execution
|
||||
this.activeExecutions.set(executionId, {
|
||||
process: /** @type {any} */ (execution).process,
|
||||
ws
|
||||
});
|
||||
|
||||
// Send the update command after a delay to ensure we're in the container
|
||||
setTimeout(() => {
|
||||
/** @type {any} */ (execution).process.write('update\n');
|
||||
}, 4000);
|
||||
|
||||
} catch (error) {
|
||||
this.sendMessage(ws, {
|
||||
type: 'error',
|
||||
data: `SSH execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TerminalHandler removed - not used by current application
|
||||
|
||||
Reference in New Issue
Block a user