fix(deploy): make portal installer runtime portable

This commit is contained in:
DeCentN2Madness authored and AIAMarketing committed 2026-11-05 00:40:18 -07:00
1 parent 4987f5ee1a
commit 36d417a422
8 files changed
+272 -132

No files matched your search

+13 -4
View File
@@ -56,6 +56,14 @@ export async function prepareResourcePlan(privateRoot, staging, sourceCommit) {
if (!files.length) fail('No deployment-approved internal resources are present; reconcile the private manifest first');
return { schema: 1, sourceCommit, files: files.sort((a, b) => `${a.role}/${a.name}`.localeCompare(`${b.role}/${b.name}`)) };
}
export function serializeResourcePlan(plan) {
if (plan?.schema !== 1 || !fullSha.test(plan.sourceCommit ?? '') || !Array.isArray(plan.files)) fail('Invalid resource installation plan');
const records = plan.files.map((entry) => {
if (!['staff', 'management'].includes(entry.role) || !safeName(entry.name) || !safeName(entry.stageName) || !/^[0-9a-f]{64}$/.test(entry.sha256 ?? '') || !Number.isInteger(entry.size) || entry.size <= 0) fail('Invalid resource installation plan entry');
return ['file', entry.role, entry.name, entry.stageName, entry.sha256, String(entry.size)].join('\t');
});
return `AIA_PORTAL_RESOURCE_PLAN_V1\nsourceCommit=${plan.sourceCommit}\n${records.join('\n')}\n`;
}
function parse(argv) {
const valid = new Set(['--dry-run','--confirm']);
if (!argv.length || argv.some((arg) => !valid.has(arg)) || (argv.includes('--dry-run') && argv.includes('--confirm'))) fail('Usage: npm run deploy:portal-resources -- --dry-run | npm run deploy:portal-resources -- --confirm');
@@ -69,14 +77,15 @@ async function main() {
const temporary = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-portal-resources-'));
try {
const staging = path.join(temporary, `portal-resources-${sha}`);
await Promise.all(['staff', 'management'].map((role) => fs.mkdir(path.join(staging, role), { recursive: true, mode: 0o700 })));
const plan = await prepareResourcePlan(privateRoot, staging, sha);
const planFile = path.join(temporary, `aia-portal-resources-${sha}.json`);
await fs.writeFile(planFile, JSON.stringify(plan, null, 2) + '\n', { mode: 0o600 });
const planFile = path.join(temporary, `aia-portal-resources-${sha}.tsv`);
await fs.writeFile(planFile, serializeResourcePlan(plan), { mode: 0o600 });
if (options.dryRun) { console.log(JSON.stringify({ dryRun: true, sourceCommit: sha, resources: plan.files.map(({ role, name, sha256, size }) => ({ role, name, sha256, size })) }, null, 2)); return; }
if (!options.confirm) fail('Refusing employee-file transfer without --confirm');
const host = process.env.AIA_DEPLOY_HOST, identity = process.env.AIA_DEPLOY_SSH_IDENTITY;
const [ssh, ...sshOptions] = sshTarget(host, identity);
const remoteStage = `${root}/staging/portal-resources-${sha}`, remotePlan = `/root/aia-portal-resources-${sha}.json`;
const remoteStage = `${root}/staging/portal-resources-${sha}`, remotePlan = `/root/aia-portal-resources-${sha}.tsv`;
const transport = `${ssh} ${sshOptions.map((item) => `'${item.replaceAll("'", "'\\''")}'`).join(' ')}`;
run(ssh, [...sshOptions, host, `install -d -m 0700 ${remoteStage}/staff ${remoteStage}/management`]);
for (const role of ['staff','management']) {
@@ -85,7 +94,7 @@ async function main() {
run('rsync', args);
}
run('rsync', ['-rlt', '--chown=root:root', '--chmod=Fu=rw,Fgo=', '-e', transport, planFile, `${host}:${remotePlan}`]);
run(ssh, [...sshOptions, host, `node ${root}/www/tools/portal-resource-install.mjs ${remoteStage} ${remotePlan}`]);
run(ssh, [...sshOptions, host, `env -i PATH=/run/current-system/sw/bin:/usr/bin:/bin HOME=/root /bin/sh ${root}/www/tools/portal-resource-install.sh ${remoteStage} ${remotePlan}`]);
console.log(JSON.stringify({ installed: true, sourceCommit: sha, resources: plan.files.length }, null, 2));
} finally { await fs.rm(temporary, { recursive: true, force: true }); }
}