81 lines
2.7 KiB
JavaScript
81 lines
2.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
export const REQUIRED_ENROLLMENT_FIELDS = Object.freeze([
|
|
'installation_id',
|
|
'ssh.host',
|
|
'ssh.port',
|
|
'ssh.user',
|
|
'ssh.certificate',
|
|
'ssh.host_key',
|
|
'database.name',
|
|
'database.username',
|
|
'database.password',
|
|
'application_admin.username',
|
|
'application_admin.password',
|
|
]);
|
|
|
|
function readNested(value, field) {
|
|
return field.split('.').reduce((current, part) => current?.[part], value);
|
|
}
|
|
|
|
export function validateEnrollmentResponse(value) {
|
|
if (!value || typeof value !== 'object') {
|
|
throw new Error('注册服务返回了无效数据');
|
|
}
|
|
for (const field of REQUIRED_ENROLLMENT_FIELDS) {
|
|
if (readNested(value, field) === undefined || readNested(value, field) === '') {
|
|
throw new Error(`注册服务缺少字段:${field}`);
|
|
}
|
|
}
|
|
if (!String(value.ssh.certificate).startsWith('ssh-ed25519-cert-v01@openssh.com ')) {
|
|
throw new Error('注册服务返回的 SSH 证书格式无效');
|
|
}
|
|
if (!/^ssh-ed25519\s+[A-Za-z0-9+/=]+(?:\s.*)?$/u.test(String(value.ssh.host_key))) {
|
|
throw new Error('注册服务返回的 SSH 主机公钥格式无效');
|
|
}
|
|
if (!/^[A-Za-z0-9_]+$/u.test(String(value.database.name))) {
|
|
throw new Error('注册服务返回的数据库名无效');
|
|
}
|
|
return value;
|
|
}
|
|
|
|
export function serializeEnvValue(value) {
|
|
const text = String(value ?? '');
|
|
if (!text) return '';
|
|
if (/[^A-Za-z0-9_./:@+-]/u.test(text) || /[#$'"\\]/u.test(text)) {
|
|
return JSON.stringify(text);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
export function mergeEnv(content, updates) {
|
|
const lines = String(content || '').split(/\r?\n/u);
|
|
const handled = new Set();
|
|
const output = lines.map((line) => {
|
|
const match = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/u);
|
|
if (!match || !(match[1] in updates)) return line;
|
|
handled.add(match[1]);
|
|
return `${match[1]}=${serializeEnvValue(updates[match[1]])}`;
|
|
});
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (!handled.has(key)) output.push(`${key}=${serializeEnvValue(value)}`);
|
|
}
|
|
while (output.length > 0 && output.at(-1) === '') output.pop();
|
|
return `${output.join('\n')}\n`;
|
|
}
|
|
|
|
export function getKnownHostsLine(host, port, hostKey) {
|
|
const target = Number(port) === 22 ? host : `[${host}]:${port}`;
|
|
return `${target} ${String(hostKey).trim()}\n`;
|
|
}
|
|
|
|
export function loadConnectionConfig(rootDirectory) {
|
|
const configPath = path.join(rootDirectory, '.remote-access', 'connection.json');
|
|
const value = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
if (!value?.installationId || !value?.ssh?.host || !value?.localDatabasePort) {
|
|
throw new Error('本地远程访问配置不完整,请重新执行 npm run remote:setup');
|
|
}
|
|
return { configPath, value };
|
|
}
|