diff --git a/scripts/check-production-api-deploy.mjs b/scripts/check-production-api-deploy.mjs index 7292a28c7..2eacaca7c 100644 --- a/scripts/check-production-api-deploy.mjs +++ b/scripts/check-production-api-deploy.mjs @@ -209,6 +209,34 @@ function assertDeployCopiesPingoraDirectReleaseDependencies() { ), 'current release 必须包含 Pingora 直连 drop-in 模板。', ); + assertFileExists( + path.join( + releaseDir, + 'deploy/systemd/genarrative-external-generation-worker@.service', + ), + 'current release 必须包含外部生成 worker systemd 模板。', + ); + assertFileExists( + path.join( + releaseDir, + 'deploy/systemd/genarrative-external-generation-controller.service', + ), + 'current release 必须包含外部生成 worker controller systemd 单元。', + ); + assertFileExists( + path.join( + fixture.systemdUnitDir, + 'genarrative-external-generation-worker@.service', + ), + 'API deploy 必须把随包外部生成 worker 模板安装到 systemd unit 目录。', + ); + assertFileExists( + path.join( + fixture.systemdUnitDir, + 'genarrative-external-generation-controller.service', + ), + 'API deploy 必须把随包外部生成 worker controller 单元安装到 systemd unit 目录。', + ); assertFileExists( path.join(releaseDir, 'deploy/pingora/pingora-gateway.env.example'), 'current release 必须包含 Pingora env 示例。', @@ -260,6 +288,11 @@ function assertDeployCopiesPingoraDirectReleaseDependencies() { ); const commandsLog = readFileSync(fixture.commandsLog, 'utf8'); + assertIncludes( + commandsLog, + 'systemctl daemon-reload', + '安装 worker systemd 单元后必须 daemon-reload。', + ); assertIncludes( commandsLog, 'systemctl restart genarrative-api.service', @@ -1136,6 +1169,7 @@ function prepareFixture(name) { const maintenanceFile = path.join(root, 'maintenance', 'enabled'); const fakeBin = path.join(root, 'bin'); const commandsLog = path.join(root, 'commands.log'); + const systemdUnitDir = path.join(root, 'etc', 'systemd', 'system'); const workerStateFile = path.join(root, 'worker-service-enabled'); const pingoraStateFile = path.join(root, 'pingora-service-active'); const version = `20260614-${name}`; @@ -1299,6 +1333,20 @@ function prepareFixture(name) { 'deploy/systemd/genarrative-pingora-gateway-direct-entry.conf', ), ); + copyFile( + 'deploy/systemd/genarrative-external-generation-worker@.service', + path.join( + sourceDir, + 'deploy/systemd/genarrative-external-generation-worker@.service', + ), + ); + copyFile( + 'deploy/systemd/genarrative-external-generation-controller.service', + path.join( + sourceDir, + 'deploy/systemd/genarrative-external-generation-controller.service', + ), + ); copyFile( 'deploy/pingora/pingora-gateway.env.example', path.join(sourceDir, 'deploy/pingora/pingora-gateway.env.example'), @@ -1360,11 +1408,11 @@ function prepareFixture(name) { ' fi', ' exit 0', 'fi', - 'if [[ "$1 $2" == "enable --now" && "${3:-}" == "genarrative-external-generation-worker@1.service" ]]; then', + 'if [[ "$1 ${2:-}" == "enable --now" && "${3:-}" == "genarrative-external-generation-worker@1.service" ]]; then', ' printf "enabled\\n" > "${worker_state_file}"', ' exit 0', 'fi', - 'if [[ "$1 $2 ${3:-}" == "is-active --quiet genarrative-pingora-gateway.service" ]]; then', + 'if [[ "$1 ${2:-} ${3:-}" == "is-active --quiet genarrative-pingora-gateway.service" ]]; then', ' if [[ "${FAKE_PINGORA_ACTIVE:-true}" == "true" || -f "${pingora_state_file}" ]]; then', ' exit 0', ' fi', @@ -1443,6 +1491,7 @@ function prepareFixture(name) { maintenanceFile, fakeBin, commandsLog, + systemdUnitDir, workerStateFile, pingoraStateFile, version, @@ -1534,6 +1583,7 @@ function runDeploy(fixture, options = {}) { FAKE_RELEASE_ROOT: fixture.releaseRoot, FAKE_RELEASE_VERSION: fixture.version, FAKE_WORKER_STATE_FILE: fixture.workerStateFile, + GENARRATIVE_SYSTEMD_UNIT_DIR: fixture.systemdUnitDir, }, }, ); diff --git a/scripts/check-production-api-release.mjs b/scripts/check-production-api-release.mjs index 652f09a32..8495269f1 100644 --- a/scripts/check-production-api-release.mjs +++ b/scripts/check-production-api-release.mjs @@ -244,6 +244,20 @@ function assertApiReleaseContainsPingoraDirectDependencies() { ), 'API release 必须包含 Pingora 直连 drop-in 模板。', ); + assertFileExists( + path.join( + releaseDir, + 'deploy/systemd/genarrative-external-generation-worker@.service', + ), + 'API release 必须包含外部生成 worker systemd 模板。', + ); + assertFileExists( + path.join( + releaseDir, + 'deploy/systemd/genarrative-external-generation-controller.service', + ), + 'API release 必须包含外部生成 worker controller systemd 单元。', + ); assertFileExists( path.join(releaseDir, 'deploy/pingora/pingora-gateway.env.example'), 'API release 必须包含 Pingora env 示例。', diff --git a/scripts/deploy/production-api-deploy.sh b/scripts/deploy/production-api-deploy.sh index abbea0b48..716ce298f 100644 --- a/scripts/deploy/production-api-deploy.sh +++ b/scripts/deploy/production-api-deploy.sh @@ -421,6 +421,54 @@ ensure_default_worker_service() { systemctl enable --now "${default_service}" } +install_release_systemd_unit() { + local source_path="$1" + local unit_name="$2" + local label="$3" + local unit_dir="${GENARRATIVE_SYSTEMD_UNIT_DIR:-/etc/systemd/system}" + + if [[ ! -f "${source_path}" ]]; then + echo "[production-api-deploy] 发布产物缺少${label}: ${source_path}" >&2 + return 1 + fi + if [[ "${unit_dir}" != /* ]]; then + echo "[production-api-deploy] systemd unit 目录必须使用绝对路径: ${unit_dir}" >&2 + return 1 + fi + + echo "[production-api-deploy] 安装${label}: ${unit_name}" + run_privileged install -d -m 0755 "${unit_dir}" + run_privileged install -m 0644 "${source_path}" "${unit_dir}/${unit_name}" +} + +install_worker_systemd_units() { + local release_dir="$1" + local pattern="$2" + local controller_service="$3" + local installed_any=0 + + if [[ "${pattern}" == "genarrative-external-generation-worker@*.service" ]]; then + install_release_systemd_unit \ + "${release_dir}/deploy/systemd/genarrative-external-generation-worker@.service" \ + "genarrative-external-generation-worker@.service" \ + "外部生成 worker systemd 模板" + installed_any=1 + fi + + if [[ "${controller_service}" == "genarrative-external-generation-controller.service" ]]; then + install_release_systemd_unit \ + "${release_dir}/deploy/systemd/genarrative-external-generation-controller.service" \ + "genarrative-external-generation-controller.service" \ + "外部生成 worker controller systemd 单元" + installed_any=1 + fi + + if [[ "${installed_any}" -eq 1 ]]; then + echo "[production-api-deploy] 重新加载 systemd unit。" + systemctl daemon-reload + fi +} + restart_worker_services() { local pattern="$1" local services=() @@ -917,6 +965,8 @@ if [[ "${PINGORA_INCLUDED}" -eq 1 ]]; then ensure_pingora_shadow_service "${PINGORA_SERVICE_NAME}" "${PINGORA_SHADOW_ENV_FILE}" fi +install_worker_systemd_units "${RELEASE_DIR}" "${WORKER_SERVICE_PATTERN}" "${WORKER_CONTROLLER_SERVICE}" + echo "[production-api-deploy] 重启服务: ${SERVICE_NAME}" systemctl restart "${SERVICE_NAME}" restart_worker_services "${WORKER_SERVICE_PATTERN}"