refactor(signing): 原生侧签名标识符改由 signing.env codegen 生成(单一真相源)

承接 scripts/signing.env(shell 单源):Xcode/Swift 侧的 Team ID/Bundle ID/App Group
等无法 source 变量、只能字面量,故用 codegen 保持单源。

- 新增 scripts/gen-signing.mjs:从 signing.env 读 3 基值,用**结构化正则**(匹配
  `DEVELOPMENT_TEAM = X`/app group 形状等,不含任何旧字面值)回填 16 个原生文件的
  标识符;--check 为 drift 模式。因不含旧值,codegen 自身无痕、未来再迁账号仍生效。
- ci/check-codegen-drift.sh 追加签名 drift 闸(gen-signing.mjs --check)。
- 全量迁到美国身份:Team 44WULXM6SV / 实体 Yanmei AI LLC / Bundle com.yanmeiai.pangolin
  (+.PacketTunnel)/ App Group macOS 原生 44WULXM6SV.com.yanmeiai.pangolin、iOS
  group.com.yanmeiai.pangolin。清理注释/ORGANIZATIONNAME 等 Apple 侧残留痕迹。
- CLAUDE.md 同步新身份 + 指向 signing.env 单源。

验证:plutil lint 6 份 entitlements/plist 全 OK;macOS/iOS pbxproj `xcodebuild -list`
可解析、Runner build settings 解析为新 Team/Bundle;gen-signing.mjs --check 幂等 0 drift;
完整 codegen drift 闸(token/l10n/proto-i18n/signing)全绿。

注:未动 Android 包名 com.pangolin.pangolin_vpn(Play 身份、自签、非 Apple 账号痕迹,
改动影响大另议)与 Windows/桌面少量元数据串。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEVUXAbFT6bF1Qw27RHWoD
This commit is contained in:
wangjia
2026-09-06 20:14:30 +08:00
parent 6618929092
commit 3fb645ed40
20 changed files with 230 additions and 68 deletions
+5 -4
View File
@@ -183,9 +183,10 @@ cd web/website && npm run gen:tokens
**系统扩展能被 realize 的硬性要求**(缺一即 `code=4` / 静默拒)
- **自包含**:`PacketTunnel` target 设 `OTHER_LDFLAGS = ""`(切断继承项目级 CocoaPods 链接标志,否则会把 `flutter_secure_storage` 链进扩展);`Libbox.xcframework` **只 Link 不 Embed**(它是静态库)。验证:`otool -L` 扩展二进制应**零 `@rpath` 外部依赖**。
- **bundle 名 = 标识符**:`PRODUCT_NAME = com.pangolin.pangolin.PacketTunnel`
- **bundle 名 = 标识符**:`PRODUCT_NAME = com.yanmeiai.pangolin.PacketTunnel`
- 扩展 `Info.plist` 必须有 **`NSSystemExtensionUsageDescription`**(网络扩展类别强制,主 app 的不顶用)。
- **App Group 用 macOS 原生格式 `<TeamID>.<name>`**(`BYL4KQHMTN.com.pangolin.pangolin`,非 iOS 的 `group.` 前缀);`NEMachServiceName` 以其为前缀。
- **App Group 用 macOS 原生格式 `<TeamID>.<name>`**(`44WULXM6SV.com.yanmeiai.pangolin`,非 iOS 的 `group.` 前缀);`NEMachServiceName` 以其为前缀。
- **签名身份单一真相源 `scripts/signing.env`**(Team ID / 实体名 / Bundle ID / App Group)。shell 脚本 source 它;原生文件(pbxproj/entitlements/plist/swift)的字面量由 `node scripts/gen-signing.mjs` 从它回填(**勿手改这些标识符**),`ci/check-codegen-drift.sh` 有 drift 闸。迁账号/改 Bundle ID 只改 signing.env 跑一次 codegen。详见 `docs/apple-account-migration-runbook.html`
- 沙箱扩展补 `network.client` / `network.server`;`get-task-allow=false` + 签名加 `--timestamp`
**libbox / NetworkExtension 集成铁律**(改 `PacketTunnelProvider.swift` 注意)
@@ -206,7 +207,7 @@ cd web/website && npm run gen:tokens
同样内嵌 libbox,与 macOS 同「CommandServer 模型」,但形态/打包不同:
- **iOS/iPad**:`NEPacketTunnelProvider` 扩展(`client/ios/PacketTunnel/`),经 `NETunnelProviderManager` 管理(非 sysext);App Group 用 **iOS 的 `group.` 前缀**(`group.com.pangolin.pangolinVpn`)。`ctl_info`/`sockaddr_ctl` iOS SDK 不发,靠 `PacketTunnel-Bridging-Header.h` 手写声明。
- **iOS/iPad**:`NEPacketTunnelProvider` 扩展(`client/ios/PacketTunnel/`),经 `NETunnelProviderManager` 管理(非 sysext);App Group 用 **iOS 的 `group.` 前缀**(`group.com.yanmeiai.pangolin`)。`ctl_info`/`sockaddr_ctl` iOS SDK 不发,靠 `PacketTunnel-Bridging-Header.h` 手写声明。
- **Android**:`PangolinVpnService`(`client/android/.../PangolinVpnService.kt`),VpnService + libbox **同进程**;明文联调 API 需 manifest `usesCleartextTraffic`(Android 9+ 默认禁 http)。
- iOS/Android 与 macOS 同走 Dart `VpnNativeBridge`,所以 Dart 侧修复(状态/统计流)四端共享。
@@ -216,7 +217,7 @@ cd web/website && npm run gen:tokens
- ⚠️ **Android libbox Java 包名是 `io.nekohasekai.libbox`**(不是 `libbox`),Kotlin import 用前者。
**真机装机(老忘——有现成脚本,别手搓 xcodebuild/签名)**:
- **iOS**:`API_URL=https://api.yanmeiai.com bash scripts/local_test.sh ipad "<设备名/id>"`(`local_test.sh ios-devices` 列已连设备)。脚本自动:flutter build ipa(**公司分发证书** Apple Distribution: Yanmei / Team `BYL4KQHMTN`,ad-hoc)→ 核验签名主体(防无声退回个人证书)→ `xcrun devicectl` 装机。`API_URL` 决定客户端连哪个控制面(prod = `https://api.yanmeiai.com`)。
- **iOS**:`API_URL=https://api.yanmeiai.com bash scripts/local_test.sh ipad "<设备名/id>"`(`local_test.sh ios-devices` 列已连设备)。脚本自动:flutter build ipa(**公司分发证书** Apple Distribution: Yanmei AI LLC / Team `44WULXM6SV`,ad-hoc)→ 核验签名主体(防无声退回个人证书)→ `xcrun devicectl` 装机。`API_URL` 决定客户端连哪个控制面(prod = `https://api.yanmeiai.com`)。
- iOS libbox 是 **gitignore 产物**,新 worktree 常缺 → 从主仓拷免重建:`cp -R /Users/wangjia/code/pangolin/client/ios/Frameworks/Libbox.xcframework client/ios/Frameworks/`(routing 等功能不碰 libbox 接口,主仓那份兼容)。
- 新设备首次装报 `0xe8008012`(描述文件不含 UDID)→ 需 xcodebuild `-allowProvisioningUpdates -allowProvisioningDeviceRegistration`(脚本 die 里给了兜底命令);chen 的设备(chen-macbook/chen-iphone)已注册。装机走**公司**分发证书,禁个人开发证书(记忆 `ios-install-team-cert`)。
+7
View File
@@ -58,3 +58,10 @@ fi
echo "→ 校验原型 data-i18n key 全 resolve (check-proto-i18n.mjs) ..."
node design/prototype/tools/check-proto-i18n.mjs
echo "✅ 原型 i18n 无漂移:I18N 块与单源一致、data-i18n 全 resolve。"
# ── 签名身份单源:scripts/signing.env → 原生文件字面量(pbxproj/entitlements/plist/swift) ──
# 迁账号/改 Bundle ID 只改 signing.env,跑 gen-signing.mjs 回填;此闸防「改了 env 没重生成」
# 或「手改了原生里的 Team/Bundle/App Group」导致签名标识符与单源分叉。
echo "→ 校验签名标识符与 scripts/signing.env 一致 (scripts/gen-signing.mjs --check) ..."
node scripts/gen-signing.mjs --check
echo "✅ 签名标识符无漂移:原生字面量与 signing.env 单源一致。"
+2 -2
View File
@@ -40,7 +40,7 @@ final class MemoryMonitor {
//
private var timer: DispatchSourceTimer?
private let queue = DispatchQueue(label: "com.pangolin.memory-monitor", qos: .utility)
private let queue = DispatchQueue(label: "com.yanmeiai.pangolin.memory-monitor", qos: .utility)
private(set) var peakResidentBytes: UInt64 = 0
private(set) var minAvailableBytes: UInt64 = UInt64.max
private(set) var sampleCount: Int = 0
@@ -48,7 +48,7 @@ final class MemoryMonitor {
init(
interval: TimeInterval = 10,
warningThreshold: UInt64 = 5 * 1024 * 1024,
subsystem: String = "com.pangolin.pangolinVpn.PacketTunnel"
subsystem: String = "com.yanmeiai.pangolin.PacketTunnel"
) {
self.interval = interval
self.warningThreshold = warningThreshold
@@ -2,15 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
PacketTunnel.entitlements — PacketTunnel Extension Target Entitlements
Bundle ID: com.pangolin.pangolinVpn.PacketTunnel
Bundle ID: com.yanmeiai.pangolin.PacketTunnel
申请说明:
1. com.apple.developer.networking.networkextension
→ Extension Bundle ID 须单独在 Apple 开发者后台注册并开启:
developer.apple.com → Identifiers → com.pangolin.pangolinVpn.PacketTunnel
developer.apple.com → Identifiers → com.yanmeiai.pangolin.PacketTunnel
→ Network Extensions → Packet Tunnel Provider(勾选)
2. com.apple.security.application-groups
→ 与主 App 使用相同 Groupgroup.com.pangolin.pangolinVpn)。
→ 与主 App 使用相同 Groupgroup.com.yanmeiai.pangolin)。
Extension 通过 UserDefaults(suiteName:) 写入最新 stats/status
主 App 在下次启动时读取(异常崩溃恢复场景)。
-->
@@ -25,7 +25,7 @@
<!-- App Group:与主 App 共享 UserDefaults / 文件容器 -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.pangolin.pangolinVpn</string>
<string>group.com.yanmeiai.pangolin</string>
</array>
</dict>
</plist>
@@ -15,9 +15,9 @@ import Network
import NetworkExtension
import os
private let log = Logger(subsystem: "com.pangolin.pangolinVpn.PacketTunnel", category: "provider")
private let log = Logger(subsystem: "com.yanmeiai.pangolin.PacketTunnel", category: "provider")
// iOS App Group group. macOS <TeamID>.
private let appGroup = "group.com.pangolin.pangolinVpn"
private let appGroup = "group.com.yanmeiai.pangolin"
final class PacketTunnelProvider: NEPacketTunnelProvider {
private var commandServer: LibboxCommandServer?
+13 -13
View File
@@ -265,7 +265,7 @@
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = com.pangolin;
ORGANIZATIONNAME = "Yanmei AI LLC";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
@@ -481,14 +481,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -610,14 +610,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -634,14 +634,14 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -657,7 +657,7 @@
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Frameworks",
@@ -670,7 +670,7 @@
"@executable_path/../../Frameworks",
);
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = PacketTunnel;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -689,7 +689,7 @@
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Frameworks",
@@ -702,7 +702,7 @@
"@executable_path/../../Frameworks",
);
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = PacketTunnel;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -721,7 +721,7 @@
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Frameworks",
@@ -734,7 +734,7 @@
"@executable_path/../../Frameworks",
);
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolinVpn.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = PacketTunnel;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
+4 -4
View File
@@ -2,16 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
Runner.entitlements — 主 App Target Entitlements
Bundle ID: com.pangolin.pangolinVpn
Bundle ID: com.yanmeiai.pangolin
申请说明:
1. com.apple.developer.networking.networkextension
→ "packet-tunnel-provider" 须在 Apple 开发者后台显式申请。
申请路径:developer.apple.com → Certificates, IDs & Profiles
→ Identifiers → com.pangolin.pangolinVpn → Capabilities
→ Identifiers → com.yanmeiai.pangolin → Capabilities
→ Network Extensions → Packet Tunnel Provider(勾选)
2. com.apple.security.application-groups
→ group.com.pangolin.pangolinVpn 须在 App Groups 注册。
→ group.com.yanmeiai.pangolin 须在 App Groups 注册。
主 App 与 PacketTunnel extension 共享同一 Group
用于 UserDefaults(suiteName:) / FileManager containerURL(forSecurityApplicationGroupIdentifier:)
传递少量共享状态(上次节点 tag、连接时间戳等)。
@@ -27,7 +27,7 @@
<!-- App Group:主 App ↔ PacketTunnel extension 共享容器 -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.pangolin.pangolinVpn</string>
<string>group.com.yanmeiai.pangolin</string>
</array>
</dict>
</plist>
+2 -2
View File
@@ -65,9 +65,9 @@ class VpnManager: NSObject {
private var tunnelManager: NETunnelProviderManager?
// Bundle ID PacketTunnel target PRODUCT_BUNDLE_IDENTIFIER
private let extensionBundleId = "com.pangolin.pangolinVpn.PacketTunnel"
private let extensionBundleId = "com.yanmeiai.pangolin.PacketTunnel"
// App Group PacketTunnel extension UserDefaults
private let appGroup = "group.com.pangolin.pangolinVpn"
private let appGroup = "group.com.yanmeiai.pangolin"
private var statusObserver: NSObjectProtocol?
+2 -2
View File
@@ -12,11 +12,11 @@
<key>NetworkExtension</key>
<dict>
<!-- NEMachServiceName 必须以扩展所属的某个 App Group 为前缀(Apple 规则)。
App Group 用 macOS 原生格式 BYL4KQHMTN.com.pangolin.pangolin,mach 名 = 该 group
App Group 用 macOS 原生格式 44WULXM6SV.com.yanmeiai.pangolin,mach 名 = 该 group
+ .PacketTunnel。参照可工作的 Tailscale:group=<Team>.io.tailscale.ipn.macsys、
mach=该 group + .network-extension。 -->
<key>NEMachServiceName</key>
<string>BYL4KQHMTN.com.pangolin.pangolin.PacketTunnel</string>
<string>44WULXM6SV.com.yanmeiai.pangolin.PacketTunnel</string>
<key>NEProviderClasses</key>
<dict>
<key>com.apple.networkextension.packet-tunnel</key>
@@ -21,7 +21,7 @@
非 iOS 的 group. 前缀——sysextd realize 前校验请求方/扩展时要求此格式。 -->
<key>com.apple.security.application-groups</key>
<array>
<string>BYL4KQHMTN.com.pangolin.pangolin</string>
<string>44WULXM6SV.com.yanmeiai.pangolin</string>
</array>
</dict>
</plist>
@@ -14,10 +14,10 @@ import Network
import NetworkExtension
import os
private let log = Logger(subsystem: "com.pangolin.pangolin.PacketTunnel", category: "provider")
private let log = Logger(subsystem: "com.yanmeiai.pangolin.PacketTunnel", category: "provider")
// macOS App Group <TeamID>.<name>( iOS group. )
// sysextd app ; group. realize
private let appGroup = "BYL4KQHMTN.com.pangolin.pangolin"
private let appGroup = "44WULXM6SV.com.yanmeiai.pangolin"
final class PacketTunnelProvider: NEPacketTunnelProvider {
private var commandServer: LibboxCommandServer?
+15 -15
View File
@@ -662,7 +662,7 @@
CURRENT_PROJECT_VERSION = 55;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pangolin_vpn.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pangolin_vpn";
@@ -677,7 +677,7 @@
CURRENT_PROJECT_VERSION = 55;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pangolin_vpn.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pangolin_vpn";
@@ -692,7 +692,7 @@
CURRENT_PROJECT_VERSION = 55;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pangolin_vpn.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/pangolin_vpn";
@@ -758,7 +758,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -893,7 +893,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -916,7 +916,7 @@
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -961,7 +961,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -982,8 +982,8 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.PacketTunnel;
PRODUCT_NAME = com.pangolin.pangolin.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = com.yanmeiai.pangolin.PacketTunnel;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -1011,7 +1011,7 @@
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -1032,8 +1032,8 @@
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--timestamp";
OTHER_LDFLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.PacketTunnel;
PRODUCT_NAME = com.pangolin.pangolin.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = com.yanmeiai.pangolin.PacketTunnel;
PROVISIONING_PROFILE_SPECIFIER = "Pangolin PacketTunnel DevID";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -1060,7 +1060,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 55;
DEVELOPMENT_TEAM = BYL4KQHMTN;
DEVELOPMENT_TEAM = 44WULXM6SV;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -1079,8 +1079,8 @@
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin.PacketTunnel;
PRODUCT_NAME = com.pangolin.pangolin.PacketTunnel;
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin.PacketTunnel;
PRODUCT_NAME = com.yanmeiai.pangolin.PacketTunnel;
PROVISIONING_PROFILE_SPECIFIER = "";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
+2 -2
View File
@@ -8,7 +8,7 @@
PRODUCT_NAME = pangolin_vpn
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.pangolin.pangolin
PRODUCT_BUNDLE_IDENTIFIER = com.yanmeiai.pangolin
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 com.pangolin. All rights reserved.
PRODUCT_COPYRIGHT = Copyright © 2026 Yanmei AI LLC. All rights reserved.
@@ -21,12 +21,12 @@
<!-- flutter_secure_storage: Data Protection Keychain 需要此 entitlement,否则返回 -34018 -->
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.pangolin.pangolin</string>
<string>$(AppIdentifierPrefix)com.yanmeiai.pangolin</string>
</array>
<!-- P1 方案B:接 System Extension 时在此加(注册 App Group 后,见 p1-macos-system-extension.md §3):
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.pangolin.pangolin</string>
<string>group.com.yanmeiai.pangolin</string>
</array>
-->
</dict>
+1 -1
View File
@@ -5,7 +5,7 @@ import ServiceManagement
class MainFlutterWindow: NSWindow {
// launch_at_startup macOS ,宿 MethodChannel Tailscale
// SMAppService.loginItem PangolinLoginHelper( --autostart app)
private let loginHelperID = "com.pangolin.pangolin.LoginHelper"
private let loginHelperID = "com.yanmeiai.pangolin.LoginHelper"
override func awakeFromNib() {
let flutterViewController = FlutterViewController()
+3 -3
View File
@@ -21,14 +21,14 @@
<!-- flutter_secure_storage: Data Protection Keychain 需要此 entitlement,否则返回 -34018 -->
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.pangolin.pangolin</string>
<string>$(AppIdentifierPrefix)com.yanmeiai.pangolin</string>
</array>
<!-- 与 PacketTunnel sysext 共享 App Group。必须用 macOS 原生格式 <TeamID>.<name>
(BYL4KQHMTN.com.pangolin.pangolin),非 iOS 的 group. 前缀——否则 sysextd 在 realize
(44WULXM6SV.com.yanmeiai.pangolin),非 iOS 的 group. 前缀——否则 sysextd 在 realize
暂存前校验请求方 app 时认定 app group 非法而拒绝(参照可工作的 Tailscale)。 -->
<key>com.apple.security.application-groups</key>
<array>
<string>BYL4KQHMTN.com.pangolin.pangolin</string>
<string>44WULXM6SV.com.yanmeiai.pangolin</string>
</array>
</dict>
</plist>
+3 -3
View File
@@ -17,15 +17,15 @@ import os.log
// os_log + %{public} Console.app / `log show` (NSLog %@
// redact <private>,)os_log(C API) macOS 10.12 ,
// Runner 10.15 :`log show --predicate 'subsystem == "com.pangolin.pangolin"'`
private let vpnLogObj = OSLog(subsystem: "com.pangolin.pangolin", category: "vpn")
// Runner 10.15 :`log show --predicate 'subsystem == "com.yanmeiai.pangolin"'`
private let vpnLogObj = OSLog(subsystem: "com.yanmeiai.pangolin", category: "vpn")
private func vpnLog(_ message: String) {
os_log("%{public}@", log: vpnLogObj, type: .default, message)
NSLog("[pangolin/vpn] %@", message) // stderr,flutter run
}
final class VpnChannel: NSObject {
private static let tunnelBundleId = "com.pangolin.pangolin.PacketTunnel"
private static let tunnelBundleId = "com.yanmeiai.pangolin.PacketTunnel"
private var statusSink: FlutterEventSink?
private var statsSink: FlutterEventSink?
+1 -1
View File
@@ -5,7 +5,7 @@
set -euo pipefail
HELPER_NAME="PangolinLoginHelper"
HELPER_ID="com.pangolin.pangolin.LoginHelper"
HELPER_ID="com.yanmeiai.pangolin.LoginHelper"
SRC="${SRCROOT}/login_helper/main.swift"
DEST="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}/Library/LoginItems/${HELPER_NAME}.app"
WORK="${DERIVED_FILE_DIR}/login_helper"
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env node
// gen-signing.mjs — 从 scripts/signing.env(单一真相源)回填所有原生文件里的
// Apple 签名标识符(Team ID / Bundle ID / App Group / 签名主体)。
//
// 为什么用 codegen 而非硬编码:Apple 工具链要求这些值以字面量出现在 pbxproj /
// entitlements / plist / swift 里,无法像 shell 那样 source 一个变量;又分散在
// 十几处,迁账号极易漏。本脚本让 signing.env 成为唯一手改点,一键回填全部字面量。
//
// 关键设计:所有匹配都是**结构化正则**(匹配 `DEVELOPMENT_TEAM = X`、native app
// group 形状等),**不含任何旧的具体值**——所以①本脚本自身无「中国账号痕迹」;
// ②未来再迁账号(改 signing.env 里的值)仍能正确回填,不依赖旧字面量。
//
// 用法:
// node scripts/gen-signing.mjs 回填(就地改文件)
// node scripts/gen-signing.mjs --check drift 闸:若有文件会被改动则退出 1(不改文件)
//
// 注:源码注释里遗留的旧标识符是内部字符串、不进产物;本脚本只管功能性字面量
// (会被编译/签名进产物的),注释去痕由一次性清理处理。
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = join(HERE, '..');
const CLIENT = join(ROOT, 'client');
// ── 读 signing.env 的 3 个基值(去引号),派生其余 ───────────────────────────
function readEnv() {
const txt = readFileSync(join(HERE, 'signing.env'), 'utf8');
const pick = (k) => {
const m = txt.match(new RegExp('^' + k + '="?([^"\\n]+)"?\\s*$', 'm'));
if (!m) throw new Error(`signing.env 缺 ${k}`);
return m[1].trim();
};
const TEAM = pick('SIGNING_TEAM_ID');
const ENTITY = pick('SIGNING_ENTITY');
const APP = pick('SIGNING_APP_BUNDLE_ID');
return {
TEAM, ENTITY, APP,
EXT: `${APP}.PacketTunnel`,
MAC_GROUP: `${TEAM}.${APP}`,
IOS_GROUP: `group.${APP}`,
};
}
const V = readEnv();
// ── 每文件的结构化替换表(路径相对 client/)──────────────────────────────────
// 每条 [正则, 替换串]。正则只描述「形状」,替换用派生值填入。
const TABLE = {
// ---- 通用:两个 pbxproj 的 Team + Bundle ID(按后缀区分 app/ext/tests)----
'macos/Runner.xcodeproj/project.pbxproj': [
[/DEVELOPMENT_TEAM = [A-Za-z0-9]+;/g, `DEVELOPMENT_TEAM = ${V.TEAM};`],
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.PacketTunnel;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.EXT};`],
[/PRODUCT_NAME = [\w.]+\.PacketTunnel;/g, `PRODUCT_NAME = ${V.EXT};`],
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.RunnerTests;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP}.RunnerTests;`],
],
'ios/Runner.xcodeproj/project.pbxproj': [
[/DEVELOPMENT_TEAM = [A-Za-z0-9]+;/g, `DEVELOPMENT_TEAM = ${V.TEAM};`],
[/PRODUCT_BUNDLE_IDENTIFIER = [\w.]+\.PacketTunnel;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.EXT};`],
// 主 app:不以 .PacketTunnel 结尾的那条(负向前瞻,避免误改扩展行)。
[/PRODUCT_BUNDLE_IDENTIFIER = (?![\w.]*\.PacketTunnel;)[\w.]+;/g, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP};`],
],
// ---- macOS xcconfig(主 app Bundle ID + 版权)----
'macos/Runner/Configs/AppInfo.xcconfig': [
[/^PRODUCT_BUNDLE_IDENTIFIER = .+$/m, `PRODUCT_BUNDLE_IDENTIFIER = ${V.APP}`],
[/^PRODUCT_COPYRIGHT = .+$/m, `PRODUCT_COPYRIGHT = Copyright © 2026 ${V.ENTITY}. All rights reserved.`],
],
// ---- macOS entitlements:app-identifier 前缀 + native app group ----
'macos/Runner/Release.entitlements': [
[/\$\(AppIdentifierPrefix\)[\w.]+/g, `$(AppIdentifierPrefix)${V.APP}`],
[/<string>[A-Z0-9]{10}\.[\w.]+<\/string>/g, `<string>${V.MAC_GROUP}</string>`],
],
'macos/Runner/DebugProfile.entitlements': [
[/\$\(AppIdentifierPrefix\)[\w.]+/g, `$(AppIdentifierPrefix)${V.APP}`],
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
],
'macos/PacketTunnel/PacketTunnel.entitlements': [
[/<string>[A-Z0-9]{10}\.[\w.]+<\/string>/g, `<string>${V.MAC_GROUP}</string>`],
],
// ---- macOS PacketTunnel Info.plist:NEMachServiceName = app group + .PacketTunnel ----
'macos/PacketTunnel/Info.plist': [
[/<string>[A-Z0-9]{10}\.[\w.]+\.PacketTunnel<\/string>/g, `<string>${V.MAC_GROUP}.PacketTunnel</string>`],
],
// ---- macOS Swift / 登录助手脚本 ----
'macos/PacketTunnel/PacketTunnelProvider.swift': [
[/subsystem: "[\w.]+"/g, `subsystem: "${V.EXT}"`],
[/appGroup = "[\w.]+"/g, `appGroup = "${V.MAC_GROUP}"`],
],
'macos/Runner/VpnChannel.swift': [
[/subsystem: "[\w.]+"/g, `subsystem: "${V.APP}"`],
[/tunnelBundleId = "[\w.]+"/g, `tunnelBundleId = "${V.EXT}"`],
],
'macos/Runner/MainFlutterWindow.swift': [
[/loginHelperID = "[\w.]+"/g, `loginHelperID = "${V.APP}.LoginHelper"`],
],
'macos/build_login_helper.sh': [
[/HELPER_ID="[\w.]+"/g, `HELPER_ID="${V.APP}.LoginHelper"`],
],
// ---- iOS entitlements(group. 前缀 app group)----
'ios/Runner/Runner.entitlements': [
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
],
'ios/PacketTunnel/PacketTunnel.entitlements': [
[/<string>group\.[\w.]+<\/string>/g, `<string>${V.IOS_GROUP}</string>`],
],
// ---- iOS Swift ----
'ios/Runner/VpnManager.swift': [
[/extensionBundleId = "[\w.]+"/g, `extensionBundleId = "${V.EXT}"`],
[/appGroup = "[\w.]+"/g, `appGroup = "${V.IOS_GROUP}"`],
],
'ios/PacketTunnel/PacketTunnelProvider.swift': [
[/subsystem: "[\w.]+"/g, `subsystem: "${V.EXT}"`],
[/appGroup = "[\w.]+"/g, `appGroup = "${V.IOS_GROUP}"`],
],
'ios/PacketTunnel/MemoryMonitor.swift': [
[/subsystem: String = "[\w.]+"/g, `subsystem: String = "${V.EXT}"`],
],
};
// ── 执行 ─────────────────────────────────────────────────────────────────────
const check = process.argv.includes('--check');
let changed = 0;
const changedFiles = [];
for (const [rel, rules] of Object.entries(TABLE)) {
const path = join(CLIENT, rel);
const before = readFileSync(path, 'utf8');
let after = before;
for (const [re, repl] of rules) after = after.replace(re, repl);
if (after !== before) {
changed++;
changedFiles.push(rel);
if (!check) writeFileSync(path, after);
}
}
if (check) {
if (changed) {
console.error(`❌ signing codegen drift:${changed} 个文件与 signing.env 不一致:`);
for (const f of changedFiles) console.error(' - ' + f);
console.error(' 跑 `node scripts/gen-signing.mjs` 回填后重试。');
process.exit(1);
}
console.log('✅ signing codegen 无 drift(原生文件与 signing.env 一致)');
} else {
console.log(`signing codegen 完成:回填 ${changed} 个文件` + (changed ? ':' : '(已一致,无改动)'));
for (const f of changedFiles) console.log(' - ' + f);
console.log(` TEAM=${V.TEAM} APP=${V.APP} MAC_GROUP=${V.MAC_GROUP} IOS_GROUP=${V.IOS_GROUP}`);
}
+9 -5
View File
@@ -1,11 +1,15 @@
# signing.env — Apple 签名身份的【单一真相源】(shell 侧)
#
# 迁移开发者账号 / 改 Bundle ID 时,改这一个文件;三个签名脚本
# (local_test.sh / ci/compile-macos.sh / ci/compile-ios.sh)全部 source 它,
# 不再各自硬编码。非机密(Team ID / 实体名 / Bundle ID 本就编进产物、公开),入 git。
# 迁移账号 / 改 Bundle ID 时,改这一个文件就够】,然后跑一次:
# node scripts/gen-signing.mjs
# 它会把这里的值回填进所有原生文件(pbxproj/entitlements/plist/swift)的字面量
# ——Apple 工具链要求这些值以字面量出现、无法 source 变量,故用 codegen 保持单源。
#
# ⚠️ Xcode 侧另有等价单一真相源 client/Signing.xcconfig(格式不同、内容须一致),
# 由 ci/check-signing-config.sh 一致性闸比对,防两处漂移。改这里也要改那里。
# 消费方:
# - 三个签名脚本(local_test.sh / ci/compile-macos.sh / ci/compile-ios.sh)直接 source 本文件;
# - 原生文件(Xcode/Swift)由 scripts/gen-signing.mjs 从本文件回填(勿手改那些标识符)。
# drift 闸:ci/check-codegen-drift.sh 跑 `gen-signing.mjs --check`,防改了 env 没重生成 /
# 手改了原生标识符。非机密(Team ID / 实体名 / Bundle ID 本就编进产物、公开),入 git。
# 10 位 Team ID(developer.apple.com → Membership details)。
SIGNING_TEAM_ID="44WULXM6SV"