diff --git a/server.js b/server.js index a4ed422..f174548 100644 --- a/server.js +++ b/server.js @@ -13,6 +13,10 @@ const http = require('http'); const https = require('https'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcrypt'); +const { exec } = require('child_process'); +const util = require('util'); +const execAsync = util.promisify(exec); +const semver = require('semver'); // For semantic version comparison // Import custom modules // Try to import with .js extension first, then fallback to no extension for better compatibility @@ -1196,6 +1200,176 @@ app.get('/api/auth/validate', authenticateJWT, (req, res) => { }); }); +// System status and update endpoints +app.get('/api/system/status', authenticateJWT, async (req, res) => { + try { + // Get system uptime + const uptimeSeconds = Math.floor(process.uptime()); + const hours = Math.floor(uptimeSeconds / 3600); + const minutes = Math.floor((uptimeSeconds % 3600) / 60); + const seconds = uptimeSeconds % 60; + const uptime = `${hours}h ${minutes}m ${seconds}s`; + + // Check transmission connection + let transmissionStatus = 'Connected'; + try { + const status = await transmissionClient.getStatus(); + if (!status.connected) { + transmissionStatus = 'Disconnected'; + } + } catch (err) { + transmissionStatus = 'Disconnected'; + } + + res.json({ + status: 'success', + data: { + version: APP_VERSION, + uptime, + transmissionStatus + } + }); + } catch (error) { + console.error('Error getting system status:', error); + res.status(500).json({ + status: 'error', + message: 'Failed to get system status' + }); + } +}); + +// Check for updates +app.get('/api/system/check-updates', authenticateJWT, async (req, res) => { + try { + // Check if git is available and if this is a git repository + const isGitRepo = fs.existsSync(path.join(__dirname, '.git')); + if (!isGitRepo) { + return res.json({ + status: 'error', + message: 'This installation is not set up for updates. Please use the bootstrap installer.' + }); + } + + // Get current version + const currentVersion = APP_VERSION; + + // Check for test mode flag which forces update availability for testing + const testMode = req.query.test === 'true'; + + if (testMode) { + // In test mode, always return that an update is available + return res.json({ + status: 'success', + data: { + updateAvailable: true, + currentVersion, + remoteVersion: '2.1.0-test', + commitsBehind: 1, + testMode: true + } + }); + } + + try { + // Normal mode - fetch latest updates without applying them + await execAsync('git fetch'); + + // Check if we're behind the remote repository + const { stdout } = await execAsync('git rev-list HEAD..origin/main --count'); + const behindCount = parseInt(stdout.trim()); + + if (behindCount > 0) { + // Get the new version from the remote package.json + const { stdout: remotePackageJson } = await execAsync('git show origin/main:package.json'); + const remotePackage = JSON.parse(remotePackageJson); + const remoteVersion = remotePackage.version; + + // Compare versions semantically - only consider it an update if remote version is higher + const isNewerVersion = semver.gt(remoteVersion, currentVersion); + + return res.json({ + status: 'success', + data: { + updateAvailable: isNewerVersion, + currentVersion, + remoteVersion, + commitsBehind: behindCount, + newerVersion: isNewerVersion + } + }); + } else { + return res.json({ + status: 'success', + data: { + updateAvailable: false, + currentVersion + } + }); + } + } catch (gitError) { + console.error('Git error checking for updates:', gitError); + + // Even if git commands fail, return a valid response with error information + return res.json({ + status: 'error', + message: 'Error checking git repository: ' + gitError.message, + data: { + updateAvailable: false, + currentVersion, + error: true, + errorDetails: gitError.message + } + }); + } + } catch (error) { + console.error('Error checking for updates:', error); + res.status(500).json({ + status: 'error', + message: 'Failed to check for updates: ' + error.message + }); + } +}); + +// Apply updates +app.post('/api/system/update', authenticateJWT, async (req, res) => { + try { + // Check if git is available and if this is a git repository + const isGitRepo = fs.existsSync(path.join(__dirname, '.git')); + if (!isGitRepo) { + return res.status(400).json({ + status: 'error', + message: 'This installation is not set up for updates. Please use the bootstrap installer.' + }); + } + + // Run the update script + const updateScriptPath = path.join(__dirname, 'scripts', 'update.sh'); + + // Make sure the update script is executable + await execAsync(`chmod +x ${updateScriptPath}`); + + // Execute the update script + const { stdout, stderr } = await execAsync(updateScriptPath); + + // If we get here, the update was successful + // The service will be restarted by the update script + res.json({ + status: 'success', + message: 'Update applied successfully. The service will restart.', + data: { + output: stdout, + errors: stderr + } + }); + } catch (error) { + console.error('Error applying update:', error); + res.status(500).json({ + status: 'error', + message: 'Failed to apply update: ' + error.message + }); + } +}); + // Catch-all route for SPA app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html'));