Skip to content

System Update

Uploads and initiates a system update process using a provided update file. This endpoint allows administrators to upload .update files to update the IPCom system.

Request

http
POST /api/update

Authentication Required: Must include JWT token in Authorization header.

Required Scope: None (any authenticated user can access this endpoint)

Content-Type: multipart/form-data

Form Field:

  • file: The update file (must be a .update file)

Request Examples

Upload Update File

bash
curl -X POST "http://your-server-ip:port/api/update" \
     -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     -F "file=@ipcom_v5.0.0_B40.update"

Using JavaScript with FormData

javascript
const fileInput = document.getElementById('updateFile');
const file = fileInput.files[0];

const formData = new FormData();
formData.append('file', file);

const response = await fetch('/api/update', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${token}`
  },
  body: formData
});

Using JavaScript with File Upload

javascript
async function uploadUpdateFile(file, token) {
  const formData = new FormData();
  formData.append('file', file);
  
  try {
    const response = await fetch('/api/update', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`
      },
      body: formData
    });
    
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }
    
    const result = await response.json();
    console.log('Update initiated successfully');
    return result;
  } catch (error) {
    console.error('Failed to upload update:', error);
    throw error;
  }
}

Response

Success (200 OK)

json
{
  "success": true
}

Error Responses

Note: Error responses are returned as plain text, not JSON.

400 Bad Request

No file provided
File upload failed

401 Unauthorized

NOT_LOGGED_IN

403 Forbidden

FORBIDDEN

405 Method Not Allowed

Method Not Allowed

500 Internal Server Error

Extraction error
Script execution failed
Failure during file handling

Update Process

Update Workflow

  1. File Upload: Update file is uploaded via multipart form data
  2. Validation: System validates the uploaded .update file
  3. Preparation: Update scripts and files are prepared
  4. Initiation: Update process is initiated asynchronously
  5. System Restart: System may restart or shutdown during update

Asynchronous Processing

  • Immediate Response: Endpoint returns success immediately after upload
  • Background Processing: Actual update process runs asynchronously
  • System Availability: System may become unavailable during update
  • No Progress Updates: Endpoint doesn't provide update progress information

Usage Examples

Basic Update Upload

javascript
async function uploadUpdate(token, updateFile) {
  const formData = new FormData();
  formData.append('file', updateFile);
  
  try {
    console.log('Uploading system update...');
    
    const response = await fetch('/api/update', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`
      },
      body: formData
    });
    
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Update upload failed: ${errorText}`);
    }
    
    const result = await response.json();
    console.log('✓ Update upload successful');
    console.log('⚠ System update initiated - system may restart');
    
    return result;
  } catch (error) {
    console.error('✗ Update upload failed:', error);
    throw error;
  }
}

Update Manager Class

javascript
class UpdateManager {
  constructor(token) {
    this.token = token;
  }
  
  async uploadUpdate(file, onProgress = null) {
    // Validate file
    if (!file) {
      throw new Error('No update file provided');
    }
    
    if (!file.name.endsWith('.update')) {
      throw new Error('Invalid file type. Must be a .update file');
    }
    
    const formData = new FormData();
    formData.append('file', file);
    
    try {
      // Log update initiation
      await this.logUpdateEvent('update_initiated', file.name);
      
      const response = await fetch('/api/update', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.token}`
        },
        body: formData
      });
      
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Upload failed: ${errorText}`);
      }
      
      const result = await response.json();
      
      // Log successful upload
      await this.logUpdateEvent('update_uploaded', file.name);
      
      console.log('✓ Update uploaded successfully');
      console.log('⚠ System update process initiated');
      console.log('⚠ System may restart or become unavailable');
      
      return result;
      
    } catch (error) {
      // Log failed upload
      await this.logUpdateEvent('update_failed', file.name, error.message);
      
      console.error('✗ Update upload failed:', error.message);
      throw error;
    }
  }
  
  async validateUpdateFile(file) {
    const validations = [];
    
    // Check file presence
    if (!file) {
      validations.push('No file selected');
    } else {
      // Check file extension
      if (!file.name.endsWith('.update')) {
        validations.push('File must have .update extension');
      }
      
      // Check file size (reasonable limits)
      const maxSize = 500 * 1024 * 1024; // 500MB
      if (file.size > maxSize) {
        validations.push('File size too large (max 500MB)');
      }
      
      if (file.size === 0) {
        validations.push('File is empty');
      }
    }
    
    return {
      isValid: validations.length === 0,
      errors: validations
    };
  }
  
  async logUpdateEvent(event, filename, error = null) {
    const logEntry = {
      timestamp: new Date().toISOString(),
      event: event,
      filename: filename,
      error: error,
      user: this.getCurrentUser()
    };
    
    console.log('UPDATE_EVENT:', JSON.stringify(logEntry));
    
    // Save to local storage
    try {
      const logs = JSON.parse(localStorage.getItem('updateLogs') || '[]');
      logs.push(logEntry);
      localStorage.setItem('updateLogs', JSON.stringify(logs));
    } catch (storageError) {
      console.warn('Could not save update log locally:', storageError.message);
    }
  }
  
  getCurrentUser() {
    try {
      const token = localStorage.getItem('authToken');
      const payload = JSON.parse(atob(token.split('.')[1]));
      return payload.username || payload.sub;
    } catch {
      return 'unknown';
    }
  }
}

// Usage
const updateManager = new UpdateManager(authToken);

// File selection and upload
const fileInput = document.getElementById('updateFile');
fileInput.addEventListener('change', async (event) => {
  const file = event.target.files[0];
  
  if (file) {
    // Validate file
    const validation = await updateManager.validateUpdateFile(file);
    
    if (!validation.isValid) {
      console.error('File validation failed:', validation.errors);
      return;
    }
    
    // Upload update
    try {
      await updateManager.uploadUpdate(file);
    } catch (error) {
      console.error('Upload failed:', error.message);
    }
  }
});

Update UI Component

javascript
class UpdateUploadUI {
  constructor(updateManager) {
    this.updateManager = updateManager;
    this.createUI();
  }
  
  createUI() {
    const container = document.getElementById('update-container');
    
    container.innerHTML = `
      <div class="update-upload">
        <h3>System Update</h3>
        <div class="file-input-wrapper">
          <input type="file" id="updateFile" accept=".update" />
          <label for="updateFile">Select Update File (.update)</label>
        </div>
        <div class="file-info" id="fileInfo" style="display: none;"></div>
        <div class="validation-errors" id="validationErrors" style="display: none;"></div>
        <button id="uploadBtn" disabled>Upload Update</button>
        <div class="progress" id="uploadProgress" style="display: none;">
          <div class="progress-bar"></div>
          <span class="progress-text">Uploading...</span>
        </div>
        <div class="warnings" id="updateWarnings" style="display: none;">
          <h4>⚠ Important Warnings:</h4>
          <ul>
            <li>System will restart during update process</li>
            <li>All services will be temporarily unavailable</li>
            <li>Ensure no critical operations are running</li>
            <li>Update process cannot be cancelled once started</li>
          </ul>
        </div>
      </div>
    `;
    
    this.bindEvents();
  }
  
  bindEvents() {
    const fileInput = document.getElementById('updateFile');
    const uploadBtn = document.getElementById('uploadBtn');
    
    fileInput.addEventListener('change', (event) => {
      this.handleFileSelection(event.target.files[0]);
    });
    
    uploadBtn.addEventListener('click', () => {
      this.handleUpload();
    });
  }
  
  async handleFileSelection(file) {
    const fileInfo = document.getElementById('fileInfo');
    const validationErrors = document.getElementById('validationErrors');
    const uploadBtn = document.getElementById('uploadBtn');
    const warnings = document.getElementById('updateWarnings');
    
    if (!file) {
      fileInfo.style.display = 'none';
      validationErrors.style.display = 'none';
      uploadBtn.disabled = true;
      warnings.style.display = 'none';
      return;
    }
    
    // Show file info
    fileInfo.innerHTML = `
      <strong>Selected File:</strong> ${file.name}<br>
      <strong>Size:</strong> ${this.formatFileSize(file.size)}<br>
      <strong>Type:</strong> ${file.type || 'Unknown'}
    `;
    fileInfo.style.display = 'block';
    
    // Validate file
    const validation = await this.updateManager.validateUpdateFile(file);
    
    if (!validation.isValid) {
      validationErrors.innerHTML = `
        <strong>Validation Errors:</strong>
        <ul>
          ${validation.errors.map(error => `<li>${error}</li>`).join('')}
        </ul>
      `;
      validationErrors.style.display = 'block';
      uploadBtn.disabled = true;
      warnings.style.display = 'none';
    } else {
      validationErrors.style.display = 'none';
      uploadBtn.disabled = false;
      warnings.style.display = 'block';
    }
    
    this.selectedFile = file;
  }
  
  async handleUpload() {
    if (!this.selectedFile) {
      return;
    }
    
    const uploadBtn = document.getElementById('uploadBtn');
    const progress = document.getElementById('uploadProgress');
    
    // Show confirmation dialog
    const confirmed = confirm(
      'Are you sure you want to start the system update?\n\n' +
      'WARNING: The system will restart and become temporarily unavailable.\n' +
      'This action cannot be undone once started.'
    );
    
    if (!confirmed) {
      return;
    }
    
    try {
      uploadBtn.disabled = true;
      progress.style.display = 'block';
      
      await this.updateManager.uploadUpdate(this.selectedFile);
      
      // Show success message
      progress.innerHTML = `
        <div class="success-message">
          <h4>✓ Update Uploaded Successfully</h4>
          <p>System update process has been initiated.</p>
          <p><strong>The system will restart shortly.</strong></p>
          <p>Please wait for the system to come back online.</p>
        </div>
      `;
      
    } catch (error) {
      // Show error message
      progress.innerHTML = `
        <div class="error-message">
          <h4>✗ Upload Failed</h4>
          <p>${error.message}</p>
        </div>
      `;
      
      uploadBtn.disabled = false;
    }
  }
  
  formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
}

// Initialize UI
const updateUI = new UpdateUploadUI(updateManager);

Error Handling and Recovery

javascript
class UpdateErrorHandler {
  constructor(updateManager) {
    this.updateManager = updateManager;
  }
  
  async handleUpdateError(error, context) {
    const errorInfo = {
      message: error.message,
      context: context,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent
    };
    
    console.error('UPDATE_ERROR:', errorInfo);
    
    // Categorize error types
    if (error.message.includes('No file provided')) {
      return this.handleFileError('No file was selected for upload');
    } else if (error.message.includes('File upload failed')) {
      return this.handleUploadError('File upload process failed');
    } else if (error.message.includes('Extraction error')) {
      return this.handleFileError('Update file could not be extracted');
    } else if (error.message.includes('Script execution failed')) {
      return this.handleSystemError('Update script execution failed');
    } else if (error.message.includes('Method Not Allowed')) {
      return this.handleRequestError('Invalid request method - use POST');
    } else {
      return this.handleGenericError(error.message);
    }
  }
  
  handleFileError(message) {
    return {
      type: 'file_error',
      message: message,
      suggestions: [
        'Verify the update file is not corrupted',
        'Ensure the file has .update extension',
        'Try downloading the update file again',
        'Contact support if the problem persists'
      ]
    };
  }
  
  handleUploadError(message) {
    return {
      type: 'upload_error',
      message: message,
      suggestions: [
        'Check network connection',
        'Verify sufficient disk space on server',
        'Try uploading again',
        'Use a smaller update file if available'
      ]
    };
  }
  
  handleSystemError(message) {
    return {
      type: 'system_error',
      message: message,
      suggestions: [
        'Contact system administrator',
        'Verify system configuration',
        'Check server logs for details',
        'Restart system services if possible'
      ]
    };
  }
  
  handleRequestError(message) {
    return {
      type: 'request_error',
      message: message,
      suggestions: [
        'Verify API endpoint is correct',
        'Check request headers and method',
        'Ensure authentication token is valid',
        'Review API documentation'
      ]
    };
  }
  
  handleGenericError(message) {
    return {
      type: 'generic_error',
      message: message,
      suggestions: [
        'Try the operation again',
        'Check system status',
        'Contact support with error details',
        'Review recent system changes'
      ]
    };
  }
}

// Usage with error handling
async function safeUpdateUpload(file, token) {
  const updateManager = new UpdateManager(token);
  const errorHandler = new UpdateErrorHandler(updateManager);
  
  try {
    return await updateManager.uploadUpdate(file);
  } catch (error) {
    const errorInfo = await errorHandler.handleUpdateError(error, 'update_upload');
    
    console.error(`Error Type: ${errorInfo.type}`);
    console.error(`Message: ${errorInfo.message}`);
    console.error('Suggestions:', errorInfo.suggestions);
    
    throw error;
  }
}

Security Considerations

Access Control

  • Authentication Required: Users must be authenticated to upload updates
  • No Specific Scope: Any authenticated user can upload updates
  • Administrative Use: Intended for administrative system updates

Update Safety

javascript
async function secureUpdateUpload(file, token, userContext) {
  // Additional security checks
  if (!userContext.isAdmin) {
    console.warn('Non-admin user attempting update upload');
    // Consider implementing additional restrictions
  }
  
  // Verify file integrity if possible
  if (file.size > 1024 * 1024 * 1024) { // 1GB limit
    throw new Error('Update file too large - possible security risk');
  }
  
  // Log the update attempt
  console.log('UPDATE_SECURITY_LOG:', {
    user: userContext.username,
    filename: file.name,
    fileSize: file.size,
    timestamp: new Date().toISOString(),
    ipAddress: userContext.ipAddress
  });
  
  return await uploadUpdate(token, file);
}

System Impact

  • Service Interruption: System will restart during update
  • Data Safety: Ensure all critical data is saved before update
  • Backup Recommended: Create system backup before major updates
  • Recovery Planning: Have rollback procedures ready

Important Notes

  • Authentication Required: Users must be authenticated but no specific scope is needed
  • File Type Restriction: Only .update files are accepted
  • Plain Text Errors: Error responses are plain text, not JSON
  • Asynchronous Process: Update process runs in background after upload
  • System Restart: System may restart or shutdown during update
  • No Progress Updates: Endpoint doesn't provide update progress information
  • Administrative Use: Intended for system administrators and maintenance
  • One-way Process: Update cannot be cancelled once initiated
  • Network Considerations: Large files may require stable network connection
  • System Requirements: Ensure sufficient disk space and system resources

Released under the MIT License.