88 lines
3.9 KiB
JavaScript
88 lines
3.9 KiB
JavaScript
// scripts/deploy-site-to-nas.js
|
|
const { spawnSync } = require('child_process');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const MOBILE_RELEASE_PATTERN = /(?:\.apk|\.aab|android[^/\\]*\.zip|signed[^/\\]*\.zip)$/i;
|
|
const MOBILE_DOWNLOAD_REFERENCE = /(?:d3ro-voice[^"']*\.apk|git\.chanpaca\.net\/attachments\/(?:0b015367-dd8b-488c-8cc0-4db413b51792|d2e1b123-5678-496a-bf74-bc188938c999))/i;
|
|
|
|
function assertNoMobileArtifacts(root) {
|
|
if (!fs.existsSync(root)) return;
|
|
const pending = [root];
|
|
while (pending.length > 0) {
|
|
const current = pending.pop();
|
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
const candidate = path.join(current, entry.name);
|
|
if (entry.isSymbolicLink()) throw new Error(`Refusing symlink in deploy tree: ${candidate}`);
|
|
if (entry.isDirectory()) pending.push(candidate);
|
|
if (entry.isFile() && MOBILE_RELEASE_PATTERN.test(entry.name)) {
|
|
throw new Error(`Unsealed mobile artifact blocked from NAS deploy: ${candidate}`);
|
|
}
|
|
if (entry.isFile() && /\.(?:html|js|json)$/i.test(entry.name)) {
|
|
const source = fs.readFileSync(candidate, 'utf8');
|
|
if (MOBILE_DOWNLOAD_REFERENCE.test(source)) {
|
|
throw new Error(`Legacy mobile download link blocked from NAS deploy: ${candidate}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deploy() {
|
|
assertNoMobileArtifacts(path.resolve(__dirname, '../site/dist'));
|
|
console.log('=== 1. Building Site ===');
|
|
const build = spawnSync('npm', ['run', 'build', '--prefix', 'site'], { stdio: 'inherit', shell: true });
|
|
if (build.status !== 0) throw new Error('Site build failed');
|
|
|
|
assertNoMobileArtifacts(path.resolve(__dirname, '../site/dist'));
|
|
|
|
console.log('=== 2. Creating tar archive of site/dist ===');
|
|
const tarPath = path.resolve(__dirname, '../out/site-dist.tar');
|
|
if (!fs.existsSync(path.dirname(tarPath))) fs.mkdirSync(path.dirname(tarPath), { recursive: true });
|
|
|
|
// Use tar to create archive
|
|
const tarRes = spawnSync('tar', ['-cf', tarPath, '-C', 'site/dist', '.'], { stdio: 'inherit', shell: true });
|
|
if (tarRes.status !== 0) throw new Error('Tar creation failed');
|
|
console.log(`Created archive: ${tarPath} (${(fs.statSync(tarPath).size / 1024 / 1024).toFixed(2)} MB)`);
|
|
|
|
console.log('=== 3. Uploading archive to Synology NAS via SCP ===');
|
|
const scpRes = spawnSync('scp', [
|
|
'-o', 'StrictHostKeyChecking=no',
|
|
tarPath,
|
|
'yunchan@192.168.0.39:/volume1/docker/d3ro/site-dist.tar'
|
|
], { stdio: 'inherit', shell: true });
|
|
if (scpRes.status !== 0) throw new Error('SCP upload failed');
|
|
|
|
console.log('=== 4. Extracting on NAS and applying to d3ro_voice_api container ===');
|
|
const sshCmd = `
|
|
mkdir -p /volume1/docker/d3ro/wwwroot &&
|
|
tar -xf /volume1/docker/d3ro/site-dist.tar -C /volume1/docker/d3ro/wwwroot &&
|
|
docker cp /volume1/docker/d3ro/wwwroot/. d3ro_voice_api:/app/wwwroot/ &&
|
|
docker exec d3ro_voice_api ls -la /app/wwwroot &&
|
|
docker exec d3ro_voice_api find /app/wwwroot -type f \( -name '*.apk' -o -name '*.aab' \) -print -quit | grep -q . && exit 1 || true
|
|
`;
|
|
|
|
const sshRes = spawnSync('ssh', [
|
|
'-o', 'StrictHostKeyChecking=no',
|
|
'yunchan@192.168.0.39',
|
|
sshCmd
|
|
], { stdio: 'inherit', shell: true });
|
|
if (sshRes.status !== 0) throw new Error('SSH extract failed');
|
|
|
|
console.log('=== 5. Updating docker-compose.yml on NAS for persistent mount ===');
|
|
const updateComposeCmd = `
|
|
sed -i 's|- /volume1/docker/d3ro/data:/app/data|- /volume1/docker/d3ro/data:/app/data\\n - /volume1/docker/d3ro/wwwroot:/app/wwwroot|g' /volume1/docker/d3ro/docker-compose.yml || true
|
|
`;
|
|
spawnSync('ssh', ['-o', 'StrictHostKeyChecking=no', 'yunchan@192.168.0.39', updateComposeCmd], { stdio: 'inherit', shell: true });
|
|
|
|
console.log('✓ Successfully deployed site and releases to NAS!');
|
|
}
|
|
|
|
module.exports = { assertNoMobileArtifacts };
|
|
|
|
if (require.main === module) {
|
|
deploy().catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|