Repository navigation
[Idea] 将 --unsigned 本地打包路径扩展到 macOS(目前仅 win-x64) #7554
Replies: 3 comments
|
把当前维护的完整 diff 附在这里,方便团队直接评估(约 90 行改动,签名/CI 路径不受影响)。分支按约定只在本地维护、不推送到远端,因此以 diff 形式给出。其中 diff --git a/apps/desktop/scripts/electron-builder-config.mjs b/apps/desktop/scripts/electron-builder-config.mjs
index 2cee72d23d..2aec501608 100644
--- a/apps/desktop/scripts/electron-builder-config.mjs
+++ b/apps/desktop/scripts/electron-builder-config.mjs
@@ -58,12 +58,14 @@ export function createElectronBuilderConfig(
throw new Error('desktop package: DSH_DESKTOP_UNSIGNED must be 0 or 1')
}
const unsigned = env.DSH_DESKTOP_UNSIGNED === '1'
- if (unsigned && resolvedPlatform !== 'win32') throw new Error('desktop package: unsigned builds require Windows')
+ // LOCAL-UNSIGNED: allow macOS unsigned local builds (upstream: Windows only).
+ if (unsigned && resolvedPlatform !== 'win32' && resolvedPlatform !== 'darwin') throw new Error('desktop package: unsigned builds require Windows or macOS')
const packagesMacOS = targetPlatform === 'darwin' || (targetPlatform === undefined && hostPlatform === 'darwin')
const packagesWindows = resolvedPlatform === 'win32'
if (resolvedPlatform === 'win32') installWindowsDirectoryInstaller()
- const macOSSigning = packagesMacOS ? resolveMacOSSigningEnvironment(env) : undefined
- if (packagesMacOS) resolveMacOSNotarizationEnvironment(env)
+ // LOCAL-UNSIGNED: unsigned local builds carry no signing identity and no notarization credentials.
+ const macOSSigning = packagesMacOS && !unsigned ? resolveMacOSSigningEnvironment(env) : undefined
+ if (packagesMacOS && !unsigned) resolveMacOSNotarizationEnvironment(env)
const buildPaths = desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
let primaryRuntimeDestination
let dshDestination
@@ -101,7 +103,9 @@ export function createElectronBuilderConfig(
protocols: [{ name: 'DeepSeek Harness', schemes: ['dsh'] }],
extraMetadata: {
dshDesktopAppId: appId,
- dshMandatoryUpdatePolicy: policy,
+ // LOCAL-UNSIGNED: personal unsigned builds embed no mandatory-update policy; its release origin is unreachable
+ // outside DeepSeek and leaves a blank blocking auth window that traps quit.
+ ...(unsigned ? {} : { dshMandatoryUpdatePolicy: policy }),
...buildVersion === productVersion ? {} : { version: buildVersion },
...packaged === undefined ? {} : { dshBuildCommit: packaged.commit, dshBuildDirty: packaged.dirty },
},
@@ -150,12 +154,12 @@ export function createElectronBuilderConfig(
// macOS matches the application locale against this bundle, not Electron Framework resources.
extendInfo: { CFBundleLocalizations: ['en', 'zh_CN'] },
identity: macOSSigning?.signingIdentity,
- forceCodeSigning: true,
+ forceCodeSigning: !unsigned,
hardenedRuntime: true,
extendInfo: { NSMicrophoneUsageDescription: 'DeepSeek Harness uses your microphone to transcribe speech into message drafts.' },
// ASAR-unpacked native runtime files are pre-signed; PAK resources are sealed by their enclosing bundle.
signIgnore: ['/Contents/Resources/app\\.asar\\.unpacked/dsh(?:/|$)', '/Contents/Resources/runtime/primary-runtime(?:/|$)', '\\.pak$'],
- notarize: true,
+ notarize: !unsigned,
target: ['dmg', 'zip'],
},
dmg: {
@@ -201,9 +205,11 @@ export function createElectronBuilderConfig(
await verifyMacOSAppUpdateConfig(appPath, resolveMacOSAppUpdateFeed(context.packager.config.publish),
context.packager.appInfo.updaterCacheDirName)
}
- verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env))
+ // LOCAL-UNSIGNED: an unsigned local build has no signature to verify.
+ if (!unsigned) verifyMacOSSignatureAfterSign(context, macOSSigning ?? resolveMacOSSigningEnvironment(env))
},
artifactBuildCompleted: artifact => {
+ if (unsigned) return
if (!artifact.file.endsWith('.dmg')) return
return notarizeMacOSDiskImageArtifact(
artifact,
diff --git a/apps/desktop/scripts/local-unsigned-rebuild.sh b/apps/desktop/scripts/local-unsigned-rebuild.sh
new file mode 100755
index 0000000000..0ba724fca2
--- /dev/null
+++ b/apps/desktop/scripts/local-unsigned-rebuild.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# LOCAL-UNSIGNED: personal helper — upgrade, rebuild, package, and install the unsigned
+# macOS desktop app into /Applications. Lives on the local/unsigned-mac branch only;
+# never push. Requires the LOCAL-UNSIGNED packaging patches on the same branch.
+#
+# Usage:
+# ./local-unsigned-rebuild.sh # upgrade + rebuild + install to /Applications
+# ./local-unsigned-rebuild.sh --wipe-build # additionally delete node_modules and
+# # .desktop-build afterwards to reclaim disk
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
+APP_NAME="DeepSeek Harness.app"
+ARTIFACT="$ROOT/apps/desktop/.desktop-build/targets/mac-arm64/unsigned-artifacts/mac-arm64/$APP_NAME"
+INSTALLED="/Applications/$APP_NAME"
+WIPE_BUILD=0
+[ "${1:-}" = "--wipe-build" ] && WIPE_BUILD=1
+
+say() { printf '\n==> %s\n' "$*"; }
+
+say "upgrade master and rebase the local patch branch"
+git -C "$ROOT" checkout master
+git -C "$ROOT" pull --ff-only
+git -C "$ROOT" checkout local/unsigned-mac
+git -C "$ROOT" rebase master
+
+say "install dependencies, remove stale outputs, rebuild"
+cd "$ROOT"
+pnpm install
+pnpm run clean
+pnpm run build
+
+say "package the unsigned macOS app (unpacked .app)"
+cd "$ROOT/apps/desktop"
+pnpm run package:mac:arm64 --unsigned --check
+pnpm run package:mac:arm64:dir --unsigned
+if [ ! -d "$ARTIFACT" ]; then
+ echo "local rebuild: artifact missing after packaging: $ARTIFACT" >&2
+ exit 1
+fi
+
+say "quit a running instance, then replace $INSTALLED"
+osascript -e 'tell application id "com.deepseek.harness" to quit' 2>/dev/null || true
+sleep 3
+pkill -x "DeepSeek Harness" 2>/dev/null || true
+rm -rf "$INSTALLED"
+ditto "$ARTIFACT" "$INSTALLED"
+echo "installed: $INSTALLED"
+
+if [ "$WIPE_BUILD" -eq 1 ]; then
+ say "wipe build dependencies and build products (--wipe-build)"
+ cd "$ROOT"
+ rm -rf apps/desktop/.desktop-build
+ find . -name node_modules -type d -prune -exec rm -rf {} +
+ echo "wiped .desktop-build and all node_modules trees (run pnpm install before the next rebuild)"
+fi
diff --git a/apps/desktop/scripts/package-target.ts b/apps/desktop/scripts/package-target.ts
index 62f4fcbbb2..19717129f9 100644
--- a/apps/desktop/scripts/package-target.ts
+++ b/apps/desktop/scripts/package-target.ts
@@ -234,7 +234,8 @@ export function parseDesktopPackageInvocation(
})
if (positionals.length > 1) throw new Error('desktop package: expected at most one target')
const name = positionals[0] ?? hostTargetName(hostPlatform, hostArch)
- if (values.unsigned && name !== 'win-x64') throw new Error('desktop package: --unsigned requires win-x64')
+ // LOCAL-UNSIGNED: allow macOS targets for personal unsigned local builds (upstream: win-x64 only).
+ if (values.unsigned && name !== 'win-x64' && !name.startsWith('mac-')) throw new Error('desktop package: --unsigned requires win-x64 or a macOS target')
if (values.unsigned && values['prepare-only']) throw new Error('desktop package: --unsigned cannot use --prepare-only')
const requestedBuildVersion = values['build-version']?.trim()
if (values['build-version'] !== undefined && (requestedBuildVersion === undefined || requestedBuildVersion === '')) {
@@ -366,8 +367,11 @@ async function main(): Promise<void> {
recordPackagingEvent(run.directory, { type: 'macos-settings', packConcurrency: settings.packConcurrency,
downloadProxyConfigured: settings.downloadProxy !== undefined,
notarizationProxyConfigured: settings.notarizationProxy !== undefined })
- await packagingStep(run.directory, 'macos-package', () => withMacOSSigningKeychain(environment,
- signingEnvironment => packageTarget(invocation, signingEnvironment, run)), secrets)
+ // LOCAL-UNSIGNED: unsigned local builds have no p12, so no signing keychain is set up.
+ const darwinPackage = invocation.unsigned
+ ? () => packageTarget(invocation, environment, run)
+ : () => withMacOSSigningKeychain(environment, signingEnvironment => packageTarget(invocation, signingEnvironment, run))
+ await packagingStep(run.directory, 'macos-package', darwinPackage, secrets)
} else {
await packagingStep(run.directory, 'windows-package', () => packageTarget(invocation, environment, run), secrets)
}
@@ -412,6 +416,8 @@ export async function packageTarget(
...buildEnv,
DSH_DESKTOP_TARGET_PLATFORM: target.platform,
DSH_DESKTOP_TARGET_ARCH: target.arch,
+ // LOCAL-UNSIGNED: prepare steps read this to skip seed Mach-O signing on macOS.
+ ...(invocation.unsigned ? { DSH_DESKTOP_UNSIGNED: '1' } : {}),
}
const downloadEnv = macOSDownloadEnvironment(targetEnv, mac?.downloadProxy)
const electronBuilderEnv = desktopElectronBuilderEnvironment(downloadEnv, invocation.unsigned)
@@ -488,10 +494,13 @@ export async function packageTarget(
}, artifact => execute(desktopElectronBuilderArguments(target, false, artifact), electronBuilderEnv)), undefined, undefined, proxyEvent)
} else if (target.platform === 'darwin') {
await execute([...desktopElectronBuilderArguments(target, true), '--config.mac.notarize=false'], electronBuilderEnv)
- await execute(['exec', 'tsx', 'scripts/smoke-packaged-runtime.ts'], targetEnv)
- const appPath = join(buildPaths.artifacts, target.arch === 'arm64' ? 'mac-arm64' : 'mac', 'DeepSeek Harness.app')
- await withMacOSNotarizationProxy(mac?.notarizationProxy,
- () => notarizeMacOS({ appPath, ...resolveMacOSNotarizationEnvironment(environment) }), undefined, undefined, proxyEvent)
+ // LOCAL-UNSIGNED: unsigned local builds smoke the unsigned-artifacts output and skip notarization.
+ await execute(['exec', 'tsx', 'scripts/smoke-packaged-runtime.ts', ...(invocation.unsigned ? ['--unsigned'] : [])], targetEnv)
+ if (!invocation.unsigned) {
+ const appPath = join(buildPaths.artifacts, target.arch === 'arm64' ? 'mac-arm64' : 'mac', 'DeepSeek Harness.app')
+ await withMacOSNotarizationProxy(mac?.notarizationProxy,
+ () => notarizeMacOS({ appPath, ...resolveMacOSNotarizationEnvironment(environment) }), undefined, undefined, proxyEvent)
+ }
} else {
await signedStage('artifacts', () => execute(desktopElectronBuilderArguments(target, invocation.directory), electronBuilderEnv))
await execute(['exec', 'tsx', 'scripts/smoke-packaged-runtime.ts', ...(invocation.unsigned ? ['--unsigned'] : [])], targetEnv)
diff --git a/apps/desktop/scripts/prepare-dsh.ts b/apps/desktop/scripts/prepare-dsh.ts
index a89247ad18..49f92e6c04 100644
--- a/apps/desktop/scripts/prepare-dsh.ts
+++ b/apps/desktop/scripts/prepare-dsh.ts
@@ -153,7 +153,8 @@ async function main(): Promise<void> {
if (!existsSync(join(DSH_OUTPUT_ROOT, 'node_modules', '@deepseek-ai', `libreoffice-kit-${officeEngine}`, 'prebuilds.json'))) {
throw new Error(`desktop runtime: missing required LibreOffice engine ${officeEngine}`)
}
- if (process.platform === 'darwin') {
+ // LOCAL-UNSIGNED: personal unsigned builds skip seed Mach-O signing (no Developer ID certificate).
+ if (process.platform === 'darwin' && process.env.DSH_DESKTOP_UNSIGNED !== '1') {
await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'sign:dsh-native', () => signMacOSRuntime(DSH_OUTPUT_ROOT, resolveDesktopAppId(process.env), resolveMacOSSigningEnvironment(process.env), join(BUILD_PATHS.root, 'signature-cache')))
await packagingStep(process.env.DSH_DESKTOP_PACKAGING_RUN_DIR, 'sign:primary-native', () => signMacOSRuntime(join(RUNTIME_ROOT, 'primary-runtime'), resolveDesktopAppId(process.env), resolveMacOSSigningEnvironment(process.env), join(BUILD_PATHS.root, 'signature-cache')))
}
diff --git a/apps/desktop/scripts/smoke-packaged-runtime.ts b/apps/desktop/scripts/smoke-packaged-runtime.ts
index 106830b5ad..911f8f1895 100644
--- a/apps/desktop/scripts/smoke-packaged-runtime.ts
+++ b/apps/desktop/scripts/smoke-packaged-runtime.ts
@@ -11,7 +11,10 @@ const paths = resolveDesktopTargetBuildPaths()
const { values } = parseArgs({ options: { unsigned: { type: 'boolean', default: false } }, allowPositionals: false })
const target = resolveDesktopBuildTarget()
const windows = target === 'win-x64'
-if (values.unsigned && !windows) throw new Error('desktop smoke: unsigned artifacts require Windows')
+// LOCAL-UNSIGNED: allow unsigned macOS local builds alongside the official Windows path.
+if (values.unsigned && !windows && target !== 'mac-arm64' && target !== 'mac-x64') {
+ throw new Error('desktop smoke: unsigned artifacts require Windows or macOS')
+}
const artifacts = values.unsigned ? paths.unsignedArtifacts : paths.artifacts
const application = windows ? join(artifacts, 'win-unpacked')
: join(artifacts, target === 'mac-arm64' ? 'mac-arm64' : 'mac', 'DeepSeek Harness.app', 'Contents') |
|
支持这个方向 —— macOS 缺 1) 2) 3) 4) 5)干净检出上 6)删掉调用点会连锁触发 tsc 的 TS6133 我采用的另一条路线(和你改上游 config 互补):不改官方 config 的语义,而是复用工厂 + 覆盖—— 已验证的结果( 如果 |
|
Adding one item the thread does not cover, plus a vote for the direction. The documentation already claims this mode exists.
That is exactly the behaviour these patches implement, and it is unreachable today:
I read that sentence while planning a personal build, planned around skipping notarization, and only discovered otherwise at the validator — which is what brought me to this discussion. On the open question (should unsigned builds embed the mandatory-update policy): I would omit it, as these patches do for macOS. A build that is explicitly barred from upload protects nobody by carrying an update policy, and an unreachable origin leaves a blocking window that the build can never satisfy. Changing Windows unsigned to match is a behaviour change for existing local testers, so it probably deserves its own decision rather than riding along with this one. Co-Authored-By: deepseek-flash noreply@deepseek.com |
Uh oh!
There was an error while loading. Please reload this page.
背景
--unsigned打包路径目前只支持 Windows x64(apps/desktop/README.md的 "Unsigned Windows test installer"),用于无签名凭据的本地安装测试。macOS 没有对等路径:package:mac:arm64硬性要求 Developer ID 证书、公证凭据、keychain 配置,以及 prepare 阶段的 seed Mach-O 签名。没有发布证书的贡献者,无法从源码构建一个可直接安装进 /Applications 的桌面 App。我的本地实践
我在一个本地分支上维护了一组约 90 行的补丁,把现有
--unsigned路径扩展到 macOS,跟随 master rebase 使用,从 0.1.6 到 0.1.7-alpha.2 在 arm64 上一直可用。改动全部以既有的DSH_DESKTOP_UNSIGNED环境变量 /--unsigned标志为条件,签名与 CI 路径不受影响:electron-builder-config.mjs— 允许 darwin 平台使用 unsigned;unsigned 时跳过签名/公证环境解析,forceCodeSigning: false、notarize: false,并跳过 afterSign 的签名验证与 DMG 公证 hook。package-target.ts/smoke-packaged-runtime.ts—--unsigned接受mac-*target;跳过签名 keychain 包装与 notarize 步骤;smoke 检查改从unsigned-artifacts/取产物。prepare-dsh.ts— unsigned 时跳过 seed Mach-O 签名(本地没有 Developer ID 证书)。dshMandatoryUpdatePolicy(原因见下面的开放问题)。产物写入
unsigned-artifacts/,天然不可作为发布件,定位与 Windows unsigned 一致:仅限本地安装测试。携带成本(为什么希望官方支持)
每次 release 我都需要把分支 rebase 到新 master,打包流程演进时会真实撞上冲突,两个最近的例子:
dsh-api-gateway变为 desktop 生产依赖,但 electron-builder 的收集器只跟随 dependencies,共享 peer 依赖@deepseek-ai/cordis没有进入app.asar/node_modules,Electron 启动即ERR_MODULE_NOT_FOUND。我临时加了一个把 cordis 声明为生产依赖的 workaround;alpha.2 上游正式修复(直接把 cordis 加进 dependencies)后,又与这个已多余的 workaround 在 rebase 时冲突。建议
--unsigned官方扩展到 macOS target,镜像 Windows 的本地测试定位:不要求签名/公证凭据,产物写入unsigned-artifacts/,永远不具备发布上传资格。dshMandatoryUpdatePolicy。两个平台的 unsigned 行为是否应当统一(统一省略,还是统一要求提供可达 origin)?如果团队觉得方向可行,我可以把这组补丁整理成 PR 提上来。
All reactions