Files
EdgeSSH/scripts/deploy.ts
T
gdz66601 e5bc7090e2 feat(auth): add optional GitHub OAuth for a single administrator
新增可选 GitHub OAuth,互斥认证并在切换时保留管理员资料。
2026-09-22 23:05:16 +08:00

79 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { spawn } from 'node:child_process';
import { appendFile, readFile, writeFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { parse, stringify } from 'smol-toml';
import { ensureDatabase } from './cloudflare-d1.ts';
import { createDeploymentConfig, readDeploymentSettings } from './deployment-config.ts';
import { CloudflareApi, resolveAccountId } from './cloudflare-api.ts';
import { resolveWorkerHostname } from './cloudflare-access.ts';
import { maskSecrets, prepareEncryptionSecret, readWorkerSecretNames } from './deployment-secrets.ts';
import { prepareAuthentication, requiredAuthSecrets } from './deployment-auth.ts';
import { administratorAccountId } from './administrator.ts';
const root = fileURLToPath(new URL('../', import.meta.url));
const generatedConfig = '.wrangler.generated.toml';
const wrangler = fileURLToPath(new URL('../node_modules/wrangler/bin/wrangler.js', import.meta.url));
async function runWrangler(args: string[], input?: string): Promise<void> {
await new Promise<void>((resolve, reject) => {
const child = spawn(process.execPath, [wrangler, ...args, '--config', generatedConfig], {
cwd: root,
env: { ...process.env, CI: 'true' },
stdio: [input === undefined ? 'ignore' : 'pipe', 'inherit', 'inherit'],
});
child.on('error', reject);
child.on('close', (code) => code === 0 ? resolve() : reject(new Error(`Wrangler ${args[0]} 失败(退出码 ${code})。`)));
if (input !== undefined) {
child.stdin!.on('error', reject);
child.stdin!.end(input);
}
});
}
async function main(): Promise<void> {
const template = parse(await readFile(new URL('../wrangler.toml', import.meta.url), 'utf8'));
const settings = readDeploymentSettings(template, process.env);
if (process.argv.includes('--validate-only')) {
console.log('部署配置校验通过。');
return;
}
const api = new CloudflareApi(settings.apiToken);
settings.accountId = await resolveAccountId(api, settings.accountId);
process.env.CLOUDFLARE_ACCOUNT_ID = settings.accountId;
const existingSecrets = await readWorkerSecretNames(api, settings);
const hostname = settings.customDomain || await resolveWorkerHostname(api, settings);
const authentication = await prepareAuthentication(api, settings, hostname, existingSecrets);
const database = await ensureDatabase(settings);
const adminAccountId = await administratorAccountId(api, settings, database);
const secrets = { ...authentication.secrets, ...await prepareEncryptionSecret(api, settings, database, existingSecrets) };
maskSecrets(secrets);
const config = createDeploymentConfig(template, settings, database, {
hostname, adminAccountId, githubAdminId: authentication.githubAdminId,
});
// 临时配置放在仓库根目录,保持 assets、main、migrations_dir 的相对路径语义。
await writeFile(new URL(`../${generatedConfig}`, import.meta.url), stringify(config), 'utf8');
console.log(`部署 Worker ${settings.workerName},使用 D1 ${database.name}(${database.uuid})。`);
await runWrangler(['d1', 'migrations', 'apply', 'DB', '--remote']);
// Secret 只通过标准输入发送,不写临时文件或命令行参数;首次部署由 Wrangler 创建草稿 Worker。
if (Object.keys(secrets).length) await runWrangler(['secret', 'bulk'], JSON.stringify(secrets));
await runWrangler(['deploy']);
// Wrangler 默认保留既有 Secret;部署后再次核对名称,避免把缺少认证的版本当作成功。
const deployedSecrets = await readWorkerSecretNames(api, settings);
for (const name of ['ENCRYPTION_KEY', ...requiredAuthSecrets(settings.authProvider)]) {
if (!deployedSecrets.has(name)) throw new Error(`部署后缺少 Worker Secret:${name}。`);
}
console.log(`部署完成:https://${hostname}`);
if (process.env.GITHUB_STEP_SUMMARY) {
await appendFile(process.env.GITHUB_STEP_SUMMARY,
`## EdgeSSH 部署完成\n\n入口:https://${hostname}\n\n登录方式:${settings.authProvider}\n\n`
+ (settings.authProvider === 'github' ? `GitHub OAuth 回调地址:https://${hostname}/auth/callback\n\n` : '')
+ 'D1 已迁移,运行时 Secret 已保存在 Cloudflare。后续部署保留原加密密钥与管理员资料。\n');
}
}
main().catch((error: Error) => {
console.error(error.message);
process.exitCode = 1;
});