修复Windows维护页门禁执行
Project CI / Repository checks (push) Successful in 1m21s
Project CI / Frontend tests (push) Successful in 3m2s
Project CI / Backend tests (push) Successful in 4m1s
Project CI / Native shell tests (push) Failing after 10m34s

转换WSL bash所需的脚本、参数和环境路径
在POSIX平台校验实际0644权限并在Windows校验安装源码契约
保留维护页原子替换与符号链接防护测试
This commit is contained in:
kdletters
2026-08-13 13:56:51 +08:00
parent 1b4cb3c45e
commit 7052d3e692
+64 -12
View File
@@ -19,7 +19,14 @@ import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const repoRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
);
const bashExecutable =
process.platform === 'win32'
? 'C:\\Program Files\\Git\\bin\\bash.exe'
: 'bash';
const failures = [];
const requestedFiles = [];
@@ -48,7 +55,10 @@ function validateDefaultPage(filePath) {
}
for (const [pattern, label] of [
[/(?:今天|今晚|明天|昨天|昨日)/u, '相对日期'],
[/(?:20\d{2}[-/.年]\d{1,2}(?:[-/.月]\d{1,2}日?)?|\d{1,2}月\d{1,2}日)/u, '具体日期'],
[
/(?:20\d{2}[-/.年]\d{1,2}(?:[-/.月]\d{1,2}日?)?|\d{1,2}月\d{1,2}日)/u,
'具体日期',
],
[/(?:[01]?\d|2[0-3]):[0-5]\d/u, '具体维护时间'],
]) {
if (pattern.test(source)) {
@@ -58,15 +68,48 @@ function validateDefaultPage(filePath) {
}
function runScript(scriptPath, args, env) {
return spawnSync('bash', [scriptPath, ...args], {
const bashArgs = args.map((arg) =>
path.isAbsolute(arg) ? toBashPath(arg) : arg,
);
const bashEnv = Object.fromEntries(
Object.entries(env).map(([key, value]) => [
key,
path.isAbsolute(value) ? toBashPath(value) : value,
]),
);
const envAssignments = Object.entries(bashEnv).map(
([key, value]) => `${key}=${value}`,
);
const commandArgs = [
...envAssignments,
'bash',
toBashPath(scriptPath),
...bashArgs,
];
const command = `exec env ${commandArgs.map(shellQuote).join(' ')}`;
return spawnSync(bashExecutable, ['-c', command], {
cwd: repoRoot,
env: { ...process.env, ...env },
env: process.env,
encoding: 'utf8',
});
}
function shellQuote(value) {
return `'${String(value).replaceAll("'", `'"'"'`)}'`;
}
function toBashPath(filePath) {
const windowsDrive = /^([A-Za-z]):[\\/](.*)$/u.exec(filePath);
if (windowsDrive) {
return `/${windowsDrive[1].toLowerCase()}/${windowsDrive[2].replaceAll('\\', '/')}`;
}
return filePath;
}
function validateRuntimePageLifecycle() {
const tempRoot = mkdtempSync(path.join(os.tmpdir(), 'genarrative-maintenance-'));
const tempRoot = mkdtempSync(
path.join(os.tmpdir(), 'genarrative-maintenance-'),
);
const markerFile = path.join(tempRoot, 'state', 'enabled');
const runtimePageFile = path.join(tempRoot, 'state', 'page.html');
const sourcePageFile = path.join(tempRoot, 'announcement.html');
@@ -81,6 +124,9 @@ function validateRuntimePageLifecycle() {
if (!onScriptSource.includes('replace_file_atomically')) {
fail('maintenance-on 必须通过统一 helper 原子替换公告页和 marker。');
}
if (!onScriptSource.includes('install -m 0644')) {
fail('maintenance-on 安装运行态公告页时必须显式设置 0644 权限。');
}
if (/\bmv\s+-[^\s]*T\b/u.test(onScriptSource)) {
fail('maintenance-on 不得使用 GNU mv 专属的 -T 参数。');
}
@@ -95,7 +141,9 @@ function validateRuntimePageLifecycle() {
env,
);
if (enable.status !== 0) {
fail(`maintenance-on --page-file 执行失败: ${enable.stderr || enable.stdout}`);
fail(
`maintenance-on --page-file 执行失败: ${enable.stderr || enable.stdout}`,
);
return;
}
if (!existsSync(markerFile)) {
@@ -107,7 +155,10 @@ function validateRuntimePageLifecycle() {
if (readFileSync(runtimePageFile, 'utf8') !== announcement) {
fail('运行态公告页内容与输入文件不一致。');
}
if ((statSync(runtimePageFile).mode & 0o777) !== 0o644) {
if (
process.platform !== 'win32' &&
(statSync(runtimePageFile).mode & 0o777) !== 0o644
) {
fail('运行态公告页权限必须为 0644。');
}
}
@@ -130,7 +181,9 @@ function validateRuntimePageLifecycle() {
chmodSync(runtimePageFile, 0o644);
const genericEnable = runScript(onScript, ['generic maintenance'], env);
if (genericEnable.status !== 0) {
fail(`通用 maintenance-on 执行失败: ${genericEnable.stderr || genericEnable.stdout}`);
fail(
`通用 maintenance-on 执行失败: ${genericEnable.stderr || genericEnable.stdout}`,
);
}
if (existsSync(runtimePageFile)) {
fail('新维护窗口未提供 --page-file 时必须清理残留公告页。');
@@ -265,10 +318,9 @@ function validateGatewayConfiguration() {
}
}
for (const filePath of
requestedFiles.length > 0
? requestedFiles
: [path.join(repoRoot, 'public/maintenance.html')]) {
for (const filePath of requestedFiles.length > 0
? requestedFiles
: [path.join(repoRoot, 'public/maintenance.html')]) {
validateDefaultPage(filePath);
}