fix(system): decouple update process into standalone detached script

This commit is contained in:
choibk 2026-01-26 08:30:52 +09:00
parent 37ab4680c5
commit ab9aecd7d5

View File

@ -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 = '';
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} && `;
}
const env = readEnv();
const isWindows = process.platform === 'win32';
const backupDir = isWindows ? './backup' : '/volume1/backup/smart_ims';
// 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"`;
const shellCommand = isWindows ? `cmd.exe /c "${updateScript}"` : updateScript;
console.log(`🚀 Starting system update to ${targetTag}...`);
console.log(`Executing: ${shellCommand}`);
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;
// Build auth URL for git commands
let remoteUrl = auth.url;
if (auth.user && auth.pass) {
remoteUrl = auth.url.replace('https://', `https://${encodeURIComponent(auth.user)}:${encodeURIComponent(auth.pass)}@`);
}
console.log('✅ Update completed successfully.');
console.log(stdout);
});
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';
// 1. Generate Standalone Update Script Content
let scriptContent = '';
const scriptName = isWindows ? 'update_system.bat' : 'update_system.sh';
const scriptPath = path.join(__dirname, '../..', scriptName);
if (isWindows) {
scriptContent = `
@echo off
echo [Update] Starting update to ${targetTag}...
if not exist "${backupDir}" mkdir "${backupDir}"
echo [Update] Backing up Uploads...
tar -czvf "${backupDir}/backup_images_${timestamp}.tar.gz" server/uploads/
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'
}
);
child.unref(); // Allow node process to exit/reload without killing this child
res.json({
success: true,
message: '업데이트 프로세스가 백그라운드에서 시작되었습니다. 약 1~3분 후 시스템이 재시작됩니다.'
message: '업데이트 프로세스가 독립적으로 실행되었습니다. 시스템이 곧 재시작됩니다.'
});
});