diff --git a/docs/development/platform-upgrade-bundles.md b/docs/development/platform-upgrade-bundles.md index 0e7696b3..606fd9df 100644 --- a/docs/development/platform-upgrade-bundles.md +++ b/docs/development/platform-upgrade-bundles.md @@ -34,7 +34,7 @@ node install-bundle.cjs --root /opt/qinglong --file /absolute/path/package.tar.g QL_DIR=/opt/qinglong QL_DATA_DIR=/opt/qinglong/data bash /opt/qinglong/shell/start-simplify.sh ``` -`--install` 拒绝非空目录,不覆盖既有配置。该包安装青龙及其 Node 生产依赖;首次启动仍需已有 Python、Bash、PM2、nginx 等相应运行环境,不会在线安装或替换它们。服务启动和系统自启按原手动安装流程配置。 +`--install` 拒绝非空目录,不覆盖既有配置。该包安装青龙及其 Node 生产依赖;首次启动仍需已有 Python、Bash、OpenSSL、PM2、nginx 等相应运行环境,不会在线安装或替换它们。手动启动脚本会在启动工作进程前完成首次 gRPC 证书准备,避免慢设备生成密钥耗尽工作进程的就绪期限;已有证书继续复用。服务启动和系统自启按原手动安装流程配置。 ARMv6 glibc 小设备(例如 Raspberry Pi 1/Zero 的 Raspbian/Raspberry Pi OS 32 位系统)选择 `linux-armv6-glibc.tar.gz`,以同一安装器执行上述离线命令。该包要求 glibc >= 2.31、ARMv6 硬浮点 Node >= 20.18.1,不适用于 ARMv5、软浮点 Node 或旧版 glibc。安装和升级均不需要 Docker,也不会下载或替换 Node。 diff --git a/shell/start-simplify.sh b/shell/start-simplify.sh index de68281b..bc9bd871 100644 --- a/shell/start-simplify.sh +++ b/shell/start-simplify.sh @@ -52,6 +52,17 @@ make_dir /etc/nginx/conf.d make_dir /run/nginx fix_config +# Slow devices may need more than the worker's readiness window to generate +# first-boot certificates. Reuse the regular initializer before starting workers. +node <<'QL_PREPARE_GRPC_CERTS' +require(require('node:path').join(process.env.QL_DIR, 'static/build/config/grpcCerts.js')) + .initGrpcCerts() + .catch((error) => { + console.error(error); + process.exitCode = 1; + }); +QL_PREPARE_GRPC_CERTS + pm2 l &>/dev/null log_with_style "INFO" "$(t '🔄 2. 启动 nginx...')" diff --git a/test/back/start-certificate-preparation.test.cjs b/test/back/start-certificate-preparation.test.cjs new file mode 100644 index 00000000..827600e2 --- /dev/null +++ b/test/back/start-certificate-preparation.test.cjs @@ -0,0 +1,53 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { spawnSync } = require('node:child_process'); + +const startup = fs.readFileSync('shell/start-simplify.sh', 'utf8'); +const preparation = startup.slice( + startup.indexOf('\nfix_config\n'), + startup.indexOf('log_with_style "INFO" "$(t \'🔄'), +); +for (const fails of [false, true]) { + test(`manual startup ${ + fails + ? 'stops on certificate failure' + : 'awaits certificates before starting supervision' + }`, (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-start-certs-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const module = path.join(root, 'static/build/config/grpcCerts.js'); + fs.mkdirSync(path.dirname(module), { recursive: true }); + fs.writeFileSync( + module, + `exports.initGrpcCerts = () => new Promise((resolve, reject) => setTimeout(() => { + if (process.env.QA_CERT_FAILURE === '1') return reject(new Error('certificate preparation failed')); + require('node:fs').writeFileSync(require('node:path').join(process.env.QL_DIR, 'certs-ready'), 'ready'); + resolve(); + }, 50));`, + ); + const result = spawnSync( + '/bin/bash', + [ + '-c', + `set -e +fix_config(){ :; } +pm2(){ test -f "$QL_DIR/certs-ready"; touch "$QL_DIR/supervisor-started"; } +${preparation}`, + ], + { + encoding: 'utf8', + env: { + ...process.env, + QL_DIR: root, + QA_CERT_FAILURE: fails ? '1' : '0', + }, + }, + ); + assert.equal(result.status, fails ? 1 : 0, result.stderr); + assert.equal(fs.existsSync(path.join(root, 'supervisor-started')), !fails); + if (fails) assert.match(result.stderr, /certificate preparation failed/); + }); +}