From 7e3ee098725bdfb62eebe2212bc5eda2ddcd48bb Mon Sep 17 00:00:00 2001 From: winewill Date: Wed, 28 Oct 2026 11:04:19 +0800 Subject: [PATCH] docs(desktop): add CI publication configuration templates --- .../publishing/macos/production.json.example | 13 ++++++++ .../config/publishing/macos/test.json.example | 9 +++++ .../windows/production.json.example | 12 +++++++ .../publishing/windows/test.json.example | 8 +++++ apps/desktop/publishing/ci/README.i18n.yaml | 4 +-- apps/desktop/publishing/ci/README.md | 2 ++ apps/desktop/publishing/ci/README.zh.md | 2 ++ .../tests/desktop-publish-templates.spec.ts | 33 +++++++++++++++++++ 8 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 apps/desktop/config/publishing/macos/production.json.example create mode 100644 apps/desktop/config/publishing/macos/test.json.example create mode 100644 apps/desktop/config/publishing/windows/production.json.example create mode 100644 apps/desktop/config/publishing/windows/test.json.example create mode 100644 apps/desktop/tests/desktop-publish-templates.spec.ts diff --git a/apps/desktop/config/publishing/macos/production.json.example b/apps/desktop/config/publishing/macos/production.json.example new file mode 100644 index 0000000000..cb7f67a0a4 --- /dev/null +++ b/apps/desktop/config/publishing/macos/production.json.example @@ -0,0 +1,13 @@ +{ + "bucket": "replace-with-bucket-1250000000", + "origin": "https://download.deepseek.com", + "credentialsFile": "/absolute/path/to/packaging/secrets/cos-production.json", + "macOS": { + "signingIdentity": "REPLACE_WITH_DEVELOPER_ID_APPLICATION_IDENTITY", + "teamId": "XXXXXXXXXX" + }, + "githubApp": { + "clientId": "REPLACE_WITH_GITHUB_APP_CLIENT_ID", + "privateKeyFile": "/absolute/path/to/packaging/publishing/keys/desktop-release-tagger.pem" + } +} diff --git a/apps/desktop/config/publishing/macos/test.json.example b/apps/desktop/config/publishing/macos/test.json.example new file mode 100644 index 0000000000..6981a88c6b --- /dev/null +++ b/apps/desktop/config/publishing/macos/test.json.example @@ -0,0 +1,9 @@ +{ + "bucket": "replace-with-bucket-1250000000", + "origin": "https://download-test.example.com", + "credentialsFile": "/absolute/path/to/packaging/secrets/cos-test.json", + "macOS": { + "signingIdentity": "REPLACE_WITH_DEVELOPER_ID_APPLICATION_IDENTITY", + "teamId": "XXXXXXXXXX" + } +} diff --git a/apps/desktop/config/publishing/windows/production.json.example b/apps/desktop/config/publishing/windows/production.json.example new file mode 100644 index 0000000000..9a2a743227 --- /dev/null +++ b/apps/desktop/config/publishing/windows/production.json.example @@ -0,0 +1,12 @@ +{ + "bucket": "replace-with-bucket-1250000000", + "origin": "https://download.deepseek.com", + "credentialsFile": "C:/absolute/path/to/packaging/secrets/cos-production.json", + "windows": { + "certificateThumbprint": "0000000000000000000000000000000000000000" + }, + "githubApp": { + "clientId": "REPLACE_WITH_GITHUB_APP_CLIENT_ID", + "privateKeyFile": "C:/absolute/path/to/packaging/publishing/keys/desktop-release-tagger.pem" + } +} diff --git a/apps/desktop/config/publishing/windows/test.json.example b/apps/desktop/config/publishing/windows/test.json.example new file mode 100644 index 0000000000..cf3e1a40a1 --- /dev/null +++ b/apps/desktop/config/publishing/windows/test.json.example @@ -0,0 +1,8 @@ +{ + "bucket": "replace-with-bucket-1250000000", + "origin": "https://download-test.example.com", + "credentialsFile": "C:/absolute/path/to/packaging/secrets/cos-test.json", + "windows": { + "certificateThumbprint": "0000000000000000000000000000000000000000" + } +} diff --git a/apps/desktop/publishing/ci/README.i18n.yaml b/apps/desktop/publishing/ci/README.i18n.yaml index d6940d4d14..0b5d6d47ce 100644 --- a/apps/desktop/publishing/ci/README.i18n.yaml +++ b/apps/desktop/publishing/ci/README.i18n.yaml @@ -21,8 +21,8 @@ en: 0478d8e35f9a3d55 zh: 0aa3fd828bc02691 /desktop-ci-publication/machine-setup: - en: 6e7b928af17f4377 - zh: 7c559078306df3fd + en: 88bbb185410a7ac9 + zh: 1d8ab4dfd7bfc14f /desktop-ci-publication/package-and-publish: en: f7c12d343891e547 zh: da596ee1366c2046 diff --git a/apps/desktop/publishing/ci/README.md b/apps/desktop/publishing/ci/README.md index 02f1e1b6af..b769f78d84 100644 --- a/apps/desktop/publishing/ci/README.md +++ b/apps/desktop/publishing/ci/README.md @@ -54,6 +54,8 @@ For a queued job, compare the job page's requested labels with **Settings → Ac Keep `DSH_PACKAGING_CONFIG_DIR` pointed at the existing machine packaging directory. Completed attempts are copied to its sibling `releases///`; `DSH_DESKTOP_RELEASES_DIR` overrides that root with an absolute path. The inventory includes installers, update archives, blockmaps, completion metadata and a credential-free manifest. Debug packages are retained but cannot publish. Copies are checked before the attempt directory becomes visible, and an existing attempt cannot be replaced. GitHub retains the same file inventory for 14 days. Local files remain until an operator removes them; do not delete an attempt while it is being verified or published. +Start from the [macOS test](../../config/publishing/macos/test.json.example), [macOS production](../../config/publishing/macos/production.json.example), [Windows test](../../config/publishing/windows/test.json.example), or [Windows production](../../config/publishing/windows/production.json.example) template. Copy the selected file to `publishing/.json` below `DSH_PACKAGING_CONFIG_DIR`. Replace the example bucket, test origin, absolute paths, signing identity or certificate thumbprint, and production App client ID with the machine settings; keep the fixed production origin. Templates contain no credentials and do not grant COS, GitHub, or signing permissions. Store referenced secrets outside Git with access restricted to the publishing account. macOS uses the same configuration for arm64 and x64; the retained package determines the target. + Create `publishing/test.json` and, when authorized, `publishing/production.json` below the configuration directory. Each declares `bucket`, HTTPS `origin` without a path, and an absolute `credentialsFile`. The credential file contains only `secretId` and `secretKey`. macOS also requires `macOS.signingIdentity` and `macOS.teamId`; Windows requires `windows.certificateThumbprint`. Production requires the exact origin `https://download.deepseek.com` and `githubApp.clientId` plus an absolute `githubApp.privateKeyFile`. These files stay outside Git. The uploader supports permanent COS credentials, not STS session credentials. Administrators must keep test and production buckets unversioned (neither enabled nor suspended). Publication assumes this deployment prerequisite and does not call `GetBucketVersioning` or require its permission. Immutable binary PUTs retain `x-cos-forbid-overwrite`; their protection depends on administrators maintaining the bucket configuration. Set the repository variable `DESKTOP_PUBLISH_RUNNERS` to a JSON mapping of registered runner names to unique labels and targets, for example `{"mini":{"label":"dsh-mini-publish","target":["mac-arm64","mac-x64"]}}`. Assign that label to exactly that runner. A target may be a single name or a nonempty list; the Mac entry above authorizes both packaging workflows on the same machine. Windows uses target `win-x64`. The retained target must match the selected packaging workflow. The routing job checks the original successful job, and the publisher checks its own runner name against the recorded name. Machines need no shared filesystem or LAN connection to each other. diff --git a/apps/desktop/publishing/ci/README.zh.md b/apps/desktop/publishing/ci/README.zh.md index 3a635b5116..26a141aff2 100644 --- a/apps/desktop/publishing/ci/README.zh.md +++ b/apps/desktop/publishing/ci/README.zh.md @@ -54,6 +54,8 @@ job 排队时,先对照 job 页面请求的标签与 **Settings → Actions 保持 `DSH_PACKAGING_CONFIG_DIR` 指向现有机器打包目录。完成的构建复制到其同级 `releases///`;`DSH_DESKTOP_RELEASES_DIR` 可用绝对路径覆盖该根目录。文件清单包含安装包、更新归档、blockmap、完成记录和不含凭据的 manifest。调试包会留存,但不能发布。复制内容通过校验后才开放 attempt 目录,已有 attempt 不能被替换。GitHub 保留相同文件清单 14 天。本地文件保留到操作员删除;校验或发布期间不要删除对应 attempt。 +从 [macOS test](../../config/publishing/macos/test.json.example)、[macOS production](../../config/publishing/macos/production.json.example)、[Windows test](../../config/publishing/windows/test.json.example) 或 [Windows production](../../config/publishing/windows/production.json.example) 模板开始,将所选文件复制到 `DSH_PACKAGING_CONFIG_DIR` 下的 `publishing/.json`。用机器实际配置替换示例 bucket、test origin、绝对路径、签名身份或证书指纹,以及 production App client ID;保留固定的 production origin。模板不含凭据,也不授予 COS、GitHub 或签名权限。引用的秘密文件保存在 Git 之外,只允许发布账户访问。macOS 的 arm64 和 x64 共用配置,目标由留存包决定。 + 在配置目录下创建 `publishing/test.json`,获得授权后再创建 `publishing/production.json`。每份配置声明 `bucket`、不带路径的 HTTPS `origin` 和绝对路径 `credentialsFile`。凭据文件只包含 `secretId` 和 `secretKey`。macOS 还需 `macOS.signingIdentity` 和 `macOS.teamId`;Windows 需要 `windows.certificateThumbprint`。production 必须使用 origin `https://download.deepseek.com`,并提供 `githubApp.clientId` 和绝对路径 `githubApp.privateKeyFile`。这些文件不进入 Git。上传器支持长期 COS 凭据,不支持 STS 会话凭据。管理员必须维持 test 和 production bucket 未启用版本控制(不能是已启用或已暂停状态)。发布流程以此为部署前提,不调用 `GetBucketVersioning`,也不要求该权限。二进制 PUT 仍携带 `x-cos-forbid-overwrite`;防覆盖保证依赖管理员维持桶配置。 将仓库变量 `DESKTOP_PUBLISH_RUNNERS` 配置为已注册 runner 名称到唯一标签和目标的 JSON 映射,例如 `{"mini":{"label":"dsh-mini-publish","target":["mac-arm64","mac-x64"]}}`。该标签只能分配给这台 runner。目标可为单个名称或非空列表;上述 Mac 条目允许同一台机器处理两种架构的打包工作流。Windows 使用目标 `win-x64`。留存包的目标必须与所选打包工作流一致。路由 job 检查原构建成功的 job,发布器再检查自身 runner 名称与记录一致。机器之间不需要共享文件系统或局域网连接。 diff --git a/apps/desktop/tests/desktop-publish-templates.spec.ts b/apps/desktop/tests/desktop-publish-templates.spec.ts new file mode 100644 index 0000000000..fd9e507933 --- /dev/null +++ b/apps/desktop/tests/desktop-publish-templates.spec.ts @@ -0,0 +1,33 @@ +import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join, posix, win32 } from 'node:path' +import { fileURLToPath } from 'node:url' +import { expect, it } from 'vitest' +import { readDesktopPublishConfig, type DesktopPublishConfig } from '../scripts/desktop-publish-config.ts' + +for (const platform of ['macos', 'windows'] as const) { + for (const preset of ['test', 'production'] as const) { + it(`accepts the ${platform} ${preset} template after machine paths are supplied`, async () => { + const path = fileURLToPath(new URL(`../config/publishing/${platform}/${preset}.json.example`, import.meta.url)) + const template = JSON.parse(await readFile(path, 'utf8')) as DesktopPublishConfig + const paths = platform === 'windows' ? win32 : posix + expect(paths.isAbsolute(template.credentialsFile)).toBe(true) + expect(template[platform === 'macos' ? 'macOS' : 'windows']).toBeDefined() + expect(Object.hasOwn(template, 'secretId')).toBe(false) + expect(Object.hasOwn(template, 'secretKey')).toBe(false) + if (preset === 'production') expect(paths.isAbsolute(template.githubApp?.privateKeyFile ?? '')).toBe(true) + const root = await mkdtemp(join(tmpdir(), 'publish-template-')) + try { + await mkdir(join(root, 'publishing')) + // The parser uses the current host's path syntax; examples retain their target platform's paths. + const config = { ...template, credentialsFile: join(root, 'absent-credentials.json'), + ...(template.githubApp ? { githubApp: { ...template.githubApp, privateKeyFile: join(root, 'absent-key.pem') } } : {}) } + const output = join(root, 'publishing', `${preset}.json`) + await writeFile(output, JSON.stringify(config)) + await expect(readDesktopPublishConfig(root, preset)).resolves.toEqual(config) + await writeFile(output, JSON.stringify({ ...config, bucket: undefined })) + await expect(readDesktopPublishConfig(root, preset)).rejects.toThrow() + } finally { await rm(root, { recursive: true, force: true }) } + }) + } +}