fix(system): decouple update process into standalone detached script
This commit is contained in:
parent
37ab4680c5
commit
ab9aecd7d5
@ -588,46 +588,106 @@ router.post('/version/update', isAuthenticated, hasRole('admin'), async (req, re
|
||||
return res.status(400).json({ error: '업데이트할 대상 태그가 지정되지 않았습니다.' });
|
||||
}
|
||||
|
||||
// This operation is asynchronous. We start it and return a message.
|
||||
// In a real production, we might want to log this to a terminal-like view.
|
||||
|
||||
const auth = await getGiteaAuth();
|
||||
let authPrefix = '';
|
||||
const env = readEnv();
|
||||
const isWindows = process.platform === 'win32';
|
||||
const backupDir = isWindows ? './backup' : '/volume1/backup/smart_ims';
|
||||
|
||||
// Build auth URL for git commands
|
||||
let remoteUrl = auth.url;
|
||||
if (auth.user && auth.pass) {
|
||||
const authenticatedUrl = auth.url.replace('https://', `https://${encodeURIComponent(auth.user)}:${encodeURIComponent(auth.pass)}@`);
|
||||
authPrefix = `git remote set-url origin ${authenticatedUrl} && `;
|
||||
} else {
|
||||
authPrefix = `git remote set-url origin ${auth.url} && `;
|
||||
remoteUrl = auth.url.replace('https://', `https://${encodeURIComponent(auth.user)}:${encodeURIComponent(auth.pass)}@`);
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const timestamp = new Date().toISOString().replace(/[:T]/g, '-').split('.')[0];
|
||||
const dbName = env.DB_NAME || 'sokuree_platform_prod';
|
||||
const dbUser = env.DB_USER || 'choibk';
|
||||
const dbPass = env.DB_PASSWORD || '';
|
||||
const dbPort = env.DB_PORT || '3307';
|
||||
|
||||
// Build a more robust update script
|
||||
// On dev environments, pm2 might fail, so we make it optional (|| echo)
|
||||
const updateScript = isWindows
|
||||
? `${authPrefix} git fetch --tags --force && git checkout -f ${targetTag} && npm install && npm run build`
|
||||
: `${authPrefix} git fetch --tags --force && git checkout -f ${targetTag} && npm install && npm run build && cd server && npm install && pm2 reload smartims-api || echo "PM2 not found, skipping reload"`;
|
||||
// 1. Generate Standalone Update Script Content
|
||||
let scriptContent = '';
|
||||
const scriptName = isWindows ? 'update_system.bat' : 'update_system.sh';
|
||||
const scriptPath = path.join(__dirname, '../..', scriptName);
|
||||
|
||||
const shellCommand = isWindows ? `cmd.exe /c "${updateScript}"` : updateScript;
|
||||
if (isWindows) {
|
||||
scriptContent = `
|
||||
@echo off
|
||||
echo [Update] Starting update to ${targetTag}...
|
||||
if not exist "${backupDir}" mkdir "${backupDir}"
|
||||
|
||||
console.log(`🚀 Starting system update to ${targetTag}...`);
|
||||
console.log(`Executing: ${shellCommand}`);
|
||||
echo [Update] Backing up Uploads...
|
||||
tar -czvf "${backupDir}/backup_images_${timestamp}.tar.gz" server/uploads/
|
||||
|
||||
exec(shellCommand, { cwd: path.join(__dirname, '../..') }, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
console.error('❌ Update Failed:', err);
|
||||
// Sanitize output for logs
|
||||
const sanitizedErr = stderr.replace(/:[^@]+@/g, ':****@');
|
||||
console.error(sanitizedErr);
|
||||
return;
|
||||
echo [Update] Syncing Source Code...
|
||||
git fetch "${remoteUrl}" +refs/tags/*:refs/tags/* --force --prune --prune-tags
|
||||
git checkout -f ${targetTag}
|
||||
|
||||
echo [Update] Installing & Building...
|
||||
call npm install
|
||||
call npm run build
|
||||
cd server
|
||||
call npm install
|
||||
|
||||
echo [Update] Restarting Server...
|
||||
echo "Please restart your dev server manually if needed."
|
||||
`;
|
||||
} else {
|
||||
// Linux/Synology Script
|
||||
const dumpTool = '/usr/local/mariadb10/bin/mysqldump';
|
||||
scriptContent = `#!/bin/bash
|
||||
exec > >(tee -a update.log) 2>&1
|
||||
echo "[Update] Starting update to ${targetTag}..."
|
||||
mkdir -p ${backupDir}
|
||||
|
||||
echo "[Update] Backing up Database..."
|
||||
${dumpTool} -u ${dbUser} --password='${dbPass}' --port ${dbPort} ${dbName} > ${backupDir}/backup_db_${timestamp}.sql || echo "DB Backup Failed, continuing..."
|
||||
|
||||
echo "[Update] Backing up Uploads..."
|
||||
tar -czvf ${backupDir}/backup_images_${timestamp}.tar.gz server/uploads/
|
||||
|
||||
echo "[Update] Syncing Source Code..."
|
||||
git remote set-url origin "${remoteUrl}"
|
||||
git fetch origin +refs/tags/*:refs/tags/* --force --prune --prune-tags
|
||||
git checkout -f ${targetTag}
|
||||
|
||||
echo "[Update] Installing & Building..."
|
||||
npm install
|
||||
npm run build
|
||||
cd server
|
||||
npm install
|
||||
|
||||
echo "[Update] Reloading PM2..."
|
||||
pm2 reload smartims-api || echo "PM2 not found"
|
||||
echo "[Update] Done."
|
||||
`;
|
||||
}
|
||||
|
||||
// 2. Write Script to File
|
||||
try {
|
||||
fs.writeFileSync(scriptPath, scriptContent, { encoding: 'utf8', mode: 0o755 });
|
||||
} catch (err) {
|
||||
console.error('Failed to create update script:', err);
|
||||
return res.status(500).json({ error: '업데이트 스크립트 생성 실패' });
|
||||
}
|
||||
|
||||
// 3. Execute Script Detached
|
||||
console.log(`🚀 [Update] Spawning independent update process: ${scriptPath}`);
|
||||
|
||||
const child = require('child_process').spawn(
|
||||
isWindows ? 'cmd.exe' : 'bash',
|
||||
isWindows ? ['/c', scriptName] : [scriptName],
|
||||
{
|
||||
cwd: path.join(__dirname, '../..'),
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
}
|
||||
console.log('✅ Update completed successfully.');
|
||||
console.log(stdout);
|
||||
});
|
||||
);
|
||||
child.unref(); // Allow node process to exit/reload without killing this child
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '업데이트 프로세스가 백그라운드에서 시작되었습니다. 약 1~3분 후 시스템이 재시작됩니다.'
|
||||
message: '업데이트 프로세스가 독립적으로 실행되었습니다. 시스템이 곧 재시작됩니다.'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user