Remove OTA update functionality
This commit is contained in:
128
web/bin/cli.js
128
web/bin/cli.js
@@ -138,7 +138,6 @@ COMMANDS:
|
||||
stop Stop running instance(s)
|
||||
restart Stop and start the server
|
||||
status Show server status
|
||||
update Check for and install updates
|
||||
|
||||
OPTIONS:
|
||||
-p, --port Web server port (default: ${DEFAULT_PORT})
|
||||
@@ -160,7 +159,6 @@ EXAMPLES:
|
||||
openchamber stop # Stop all running instances
|
||||
openchamber stop --port 3000 # Stop specific instance
|
||||
openchamber status # Check status
|
||||
openchamber update # Update to latest version
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -862,132 +860,6 @@ const commands = {
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const os = await import('os');
|
||||
const tmpDir = os.tmpdir();
|
||||
const packageManagerPath = path.join(__dirname, '..', 'server', 'lib', 'package-manager.js');
|
||||
const {
|
||||
checkForUpdates,
|
||||
executeUpdate,
|
||||
detectPackageManager,
|
||||
getCurrentVersion,
|
||||
} = await importFromFilePath(packageManagerPath);
|
||||
|
||||
// Check for running instances before update
|
||||
let runningInstances = [];
|
||||
try {
|
||||
const files = fs.readdirSync(tmpDir);
|
||||
const pidFiles = files.filter(file => file.startsWith('openchamber-') && file.endsWith('.pid'));
|
||||
|
||||
for (const file of pidFiles) {
|
||||
const port = parseInt(file.replace('openchamber-', '').replace('.pid', ''));
|
||||
if (!isNaN(port)) {
|
||||
const pidFilePath = path.join(tmpDir, file);
|
||||
const instanceFilePath = path.join(tmpDir, `openchamber-${port}.json`);
|
||||
const pid = readPidFile(pidFilePath);
|
||||
|
||||
if (pid && isProcessRunning(pid)) {
|
||||
const storedOptions = readInstanceOptions(instanceFilePath);
|
||||
runningInstances.push({
|
||||
port,
|
||||
pid,
|
||||
pidFilePath,
|
||||
instanceFilePath,
|
||||
storedOptions: storedOptions || { port, daemon: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
console.log('Checking for updates...');
|
||||
console.log(`Current version: ${getCurrentVersion()}`);
|
||||
|
||||
const updateInfo = await checkForUpdates();
|
||||
|
||||
if (updateInfo.error) {
|
||||
console.error(`Error: ${updateInfo.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!updateInfo.available) {
|
||||
console.log('\nYou are running the latest version.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nNew version available: ${updateInfo.version}`);
|
||||
|
||||
if (updateInfo.body) {
|
||||
console.log('\nChangelog:');
|
||||
console.log('─'.repeat(40));
|
||||
// Simple formatting for CLI
|
||||
const formatted = updateInfo.body
|
||||
.replace(/^## \[(\d+\.\d+\.\d+)\] - \d{4}-\d{2}-\d{2}/gm, '\nv$1')
|
||||
.replace(/^### /gm, '\n')
|
||||
.replace(/^- /gm, ' • ');
|
||||
console.log(formatted);
|
||||
console.log('─'.repeat(40));
|
||||
}
|
||||
|
||||
// Stop running instances before update
|
||||
if (runningInstances.length > 0) {
|
||||
console.log(`\nStopping ${runningInstances.length} running instance(s) before update...`);
|
||||
for (const instance of runningInstances) {
|
||||
try {
|
||||
await requestServerShutdown(instance.port);
|
||||
process.kill(instance.pid, 'SIGTERM');
|
||||
let attempts = 0;
|
||||
while (isProcessRunning(instance.pid) && attempts < 20) {
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
attempts++;
|
||||
}
|
||||
if (isProcessRunning(instance.pid)) {
|
||||
process.kill(instance.pid, 'SIGKILL');
|
||||
}
|
||||
removePidFile(instance.pidFilePath);
|
||||
console.log(` Stopped instance on port ${instance.port}`);
|
||||
} catch (error) {
|
||||
console.warn(` Warning: Could not stop instance on port ${instance.port}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pm = detectPackageManager();
|
||||
console.log(`\nDetected package manager: ${pm}`);
|
||||
console.log('Installing update...\n');
|
||||
|
||||
const result = executeUpdate(pm);
|
||||
|
||||
if (result.success) {
|
||||
console.log('\nUpdate successful!');
|
||||
|
||||
// Restart previously running instances
|
||||
if (runningInstances.length > 0) {
|
||||
console.log(`\nRestarting ${runningInstances.length} instance(s)...`);
|
||||
for (const instance of runningInstances) {
|
||||
try {
|
||||
// Force daemon mode for restart after update
|
||||
const restartOptions = {
|
||||
...instance.storedOptions,
|
||||
daemon: true,
|
||||
};
|
||||
await commands.serve(restartOptions);
|
||||
console.log(` Restarted instance on port ${instance.port}`);
|
||||
} catch (error) {
|
||||
console.error(` Failed to restart instance on port ${instance.port}: ${error.message}`);
|
||||
console.log(` Run manually: openchamber serve --port ${instance.port} --daemon`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('\nUpdate failed.');
|
||||
console.error(`Exit code: ${result.exitCode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -6675,203 +6675,6 @@ async function main(options = {}) {
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/openchamber/update-check', async (_req, res) => {
|
||||
try {
|
||||
const { checkForUpdates } = await import('./lib/package-manager.js');
|
||||
const updateInfo = await checkForUpdates();
|
||||
res.json(updateInfo);
|
||||
} catch (error) {
|
||||
console.error('Failed to check for updates:', error);
|
||||
res.status(500).json({
|
||||
available: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to check for updates',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/openchamber/update-install', async (_req, res) => {
|
||||
try {
|
||||
const { spawn: spawnChild } = await import('child_process');
|
||||
const {
|
||||
checkForUpdates,
|
||||
getUpdateCommand,
|
||||
detectPackageManager,
|
||||
} = await import('./lib/package-manager.js');
|
||||
|
||||
// Verify update is available
|
||||
const updateInfo = await checkForUpdates();
|
||||
if (!updateInfo.available) {
|
||||
return res.status(400).json({ error: 'No update available' });
|
||||
}
|
||||
|
||||
const pm = detectPackageManager();
|
||||
const updateCmd = getUpdateCommand(pm);
|
||||
const isContainer =
|
||||
fs.existsSync('/.dockerenv') ||
|
||||
Boolean(process.env.CONTAINER) ||
|
||||
process.env.container === 'docker';
|
||||
|
||||
if (isContainer) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Update starting, server will stay online',
|
||||
version: updateInfo.version,
|
||||
packageManager: pm,
|
||||
autoRestart: false,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
console.log(`\nInstalling update using ${pm} (container mode)...`);
|
||||
console.log(`Running: ${updateCmd}`);
|
||||
|
||||
const shell = process.platform === 'win32' ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
||||
const shellFlag = process.platform === 'win32' ? '/c' : '-c';
|
||||
const child = spawnChild(shell, [shellFlag, updateCmd], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: process.env,
|
||||
});
|
||||
child.unref();
|
||||
}, 500);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current server port for restart
|
||||
const currentPort = server.address()?.port || 3000;
|
||||
|
||||
// Try to read stored instance options for restart
|
||||
const tmpDir = os.tmpdir();
|
||||
const instanceFilePath = path.join(tmpDir, `openchamber-${currentPort}.json`);
|
||||
let storedOptions = { port: currentPort, daemon: true };
|
||||
try {
|
||||
const content = await fs.promises.readFile(instanceFilePath, 'utf8');
|
||||
storedOptions = JSON.parse(content);
|
||||
} catch {
|
||||
// Use defaults
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
const quotePosix = (value) => `'${String(value).replace(/'/g, "'\\''")}'`;
|
||||
const quoteCmd = (value) => {
|
||||
const stringValue = String(value);
|
||||
return `"${stringValue.replace(/"/g, '""')}"`;
|
||||
};
|
||||
|
||||
// Build restart command using explicit runtime + CLI path.
|
||||
// Avoids relying on `openchamber` being in PATH for service environments.
|
||||
const cliPath = path.resolve(__dirname, '..', 'bin', 'cli.js');
|
||||
const restartParts = [
|
||||
isWindows ? quoteCmd(process.execPath) : quotePosix(process.execPath),
|
||||
isWindows ? quoteCmd(cliPath) : quotePosix(cliPath),
|
||||
'serve',
|
||||
'--port',
|
||||
String(storedOptions.port),
|
||||
'--daemon',
|
||||
];
|
||||
let restartCmdPrimary = restartParts.join(' ');
|
||||
let restartCmdFallback = `openchamber serve --port ${storedOptions.port} --daemon`;
|
||||
if (storedOptions.uiPassword) {
|
||||
if (isWindows) {
|
||||
// Escape for cmd.exe quoted argument
|
||||
const escapedPw = storedOptions.uiPassword.replace(/"/g, '""');
|
||||
restartCmdPrimary += ` --ui-password "${escapedPw}"`;
|
||||
restartCmdFallback += ` --ui-password "${escapedPw}"`;
|
||||
} else {
|
||||
// Escape for POSIX single-quoted argument
|
||||
const escapedPw = storedOptions.uiPassword.replace(/'/g, "'\\''");
|
||||
restartCmdPrimary += ` --ui-password '${escapedPw}'`;
|
||||
restartCmdFallback += ` --ui-password '${escapedPw}'`;
|
||||
}
|
||||
}
|
||||
const restartCmd = `(${restartCmdPrimary}) || (${restartCmdFallback})`;
|
||||
|
||||
// Respond immediately - update will happen after response
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Update starting, server will restart shortly',
|
||||
version: updateInfo.version,
|
||||
packageManager: pm,
|
||||
autoRestart: true,
|
||||
});
|
||||
|
||||
// Give time for response to be sent
|
||||
setTimeout(() => {
|
||||
console.log(`\nInstalling update using ${pm}...`);
|
||||
console.log(`Running: ${updateCmd}`);
|
||||
|
||||
// Create a script that will:
|
||||
// 1. Wait for current process to exit
|
||||
// 2. Run the update
|
||||
// 3. Restart the server with original options
|
||||
const shell = isWindows ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
||||
const shellFlag = isWindows ? '/c' : '-c';
|
||||
const script = isWindows
|
||||
? `
|
||||
timeout /t 2 /nobreak >nul
|
||||
${updateCmd}
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo Update successful, restarting OpenChamber...
|
||||
${restartCmd}
|
||||
) else (
|
||||
echo Update failed
|
||||
exit /b 1
|
||||
)
|
||||
`
|
||||
: `
|
||||
sleep 2
|
||||
${updateCmd}
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Update successful, restarting OpenChamber..."
|
||||
${restartCmd}
|
||||
else
|
||||
echo "Update failed"
|
||||
exit 1
|
||||
fi
|
||||
`;
|
||||
|
||||
// Spawn detached shell to run update after we exit.
|
||||
// Capture output to disk so restart failures are diagnosable.
|
||||
const updateLogPath = path.join(OPENCHAMBER_DATA_DIR, 'update-install.log');
|
||||
let logFd = null;
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(updateLogPath), { recursive: true });
|
||||
logFd = fs.openSync(updateLogPath, 'a');
|
||||
} catch (logError) {
|
||||
console.warn('Failed to open update log file, continuing without log capture:', logError);
|
||||
}
|
||||
|
||||
const child = spawnChild(shell, [shellFlag, script], {
|
||||
detached: true,
|
||||
stdio: logFd !== null ? ['ignore', logFd, logFd] : 'ignore',
|
||||
env: process.env,
|
||||
});
|
||||
child.unref();
|
||||
|
||||
if (logFd !== null) {
|
||||
try {
|
||||
fs.closeSync(logFd);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Update process spawned, shutting down server...');
|
||||
|
||||
// Give child process time to start, then exit
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 500);
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('Failed to install update:', error);
|
||||
res.status(500).json({
|
||||
error: error instanceof Error ? error.message : 'Failed to install update',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/openchamber/models-metadata', async (req, res) => {
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user