Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
511 changes: 511 additions & 0 deletions .github/workflows/linux-private-build.yml

Large diffs are not rendered by default.

90 changes: 8 additions & 82 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Release (Windows and macOS ARM64)
name: Release (Windows, macOS ARM64 and Linux x64)

on:
push:
Expand Down Expand Up @@ -711,10 +711,17 @@ jobs:
uses: ./.github/workflows/macos-private-build.yml
secrets: inherit

# Linux 与 Windows/macOS 同为必需平台:pin 没配齐就整个 release 失败(fail closed),
# 不允许「静默少发一个平台」。准备步骤见 linux-private-build.yml 顶部的注释。
build-linux-x64:
uses: ./.github/workflows/linux-private-build.yml
secrets: inherit

publish-release:
needs:
- build-windows
- build-macos-arm64
- build-linux-x64
runs-on: ubuntu-latest
steps:
- name: Checkout release history
Expand Down Expand Up @@ -842,84 +849,3 @@ jobs:
subprocess.run(["gh", "run", "watch", str(match["id"]), "--repo", repository, "--exit-status"], check=True)
PY

# ========================== QQ 群通知 ==========================
# 等 Release 发布完成后,发送 QQ 群通知。
# 消息内容取最后一次 commit 正文(用户约定在此写本次更新说明)。
# Windows exe 通过分块上传直传 QQ(GitHub CDN 在大陆不可访问)。
qq-notify:
needs: [publish-release]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 1
persist-credentials: false

- name: Prepare release info & download artifacts
id: prep
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ github.ref_name }}"
VER="${VERSION#v}"
EXE="WeChatDataAnalysis-${VER}-Setup.exe"
DMG="WeChatDataAnalysis-${VER}-mac-arm64.dmg"
EXE_7Z="${EXE}.7z"
DMG_7Z="${DMG}.7z"
BASE_URL="https://github.com/${{ github.repository }}/releases/download/${VERSION}"
EXE_URL="${BASE_URL}/${EXE}"
MAC_ARM64_URL="${BASE_URL}/${DMG}"

gh release download "${VERSION}" --pattern "${EXE}" --pattern "${DMG}" --dir /tmp/release

7z a /tmp/release/${EXE_7Z} /tmp/release/${EXE}
7z a /tmp/release/${DMG_7Z} /tmp/release/${DMG}

BODY=$(git log -1 --pretty=format:%b)
[ -z "$BODY" ] && BODY=$(git log -1 --pretty=format:%s)

echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "exe_url=${EXE_URL}" >> $GITHUB_OUTPUT
echo "mac_arm64_url=${MAC_ARM64_URL}" >> $GITHUB_OUTPUT
{ echo "body<<BODY_EOF"; echo "$BODY"; echo "BODY_EOF"; } >> $GITHUB_OUTPUT

MAX_BYTES=209715200
EXE_7Z_SIZE=$(stat -c%s /tmp/release/${EXE_7Z})
DMG_7Z_SIZE=$(stat -c%s /tmp/release/${DMG_7Z})

{
echo "file_path_list<<FP_EOF"
[ "${EXE_7Z_SIZE}" -le "${MAX_BYTES}" ] && echo "/tmp/release/${EXE_7Z}"
[ "${DMG_7Z_SIZE}" -le "${MAX_BYTES}" ] && echo "/tmp/release/${DMG_7Z}"
echo "FP_EOF"
} >> $GITHUB_OUTPUT

{
echo "file_name_list<<FN_EOF"
[ "${EXE_7Z_SIZE}" -le "${MAX_BYTES}" ] && echo "${EXE_7Z}"
[ "${DMG_7Z_SIZE}" -le "${MAX_BYTES}" ] && echo "${DMG_7Z}"
echo "FN_EOF"
} >> $GITHUB_OUTPUT

- name: Send QQ notification
uses: H3CoF6/qq-notify-action@50d180981e7c7b8552a3331b981e3f8cfcf40c44
with:
appid: ${{ secrets.QQ_APPID }}
secret: ${{ secrets.QQ_SECRET }}
group_openid: ${{ secrets.QQ_GROUP_OPENID }}
message: |
## WeChatDataAnalysis 新版本 ${{ steps.prep.outputs.version }} 发布

==详细更改如下:==
${{ steps.prep.outputs.body }}

---

- Windows 安装包 [下载链接](${{ steps.prep.outputs.exe_url }})
- macOS arm64 [下载链接](${{ steps.prep.outputs.mac_arm64_url }})

欢迎大家使用和测试~
file_path: ${{ steps.prep.outputs.file_path_list }}
file_type: file
file_name: ${{ steps.prep.outputs.file_name_list }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ wheels/
.ace-tool/
pnpm-lock.yaml
/tools/tmp_isaac64_compare.js
/native/wce_integrity/
/native/wce_integrity
/.claude/settings.local.json
.env
.env.*
Expand Down Expand Up @@ -87,6 +87,7 @@ pnpm-lock.yaml
/src/wechat_decrypt_tool/native/wechatdb_client.dll
/src/wechat_decrypt_tool/native/wechatdb_broker.exe
/src/wechat_decrypt_tool/native/libwechatdb_client.dylib
/src/wechat_decrypt_tool/native/libwechatdb_client.so
/src/wechat_decrypt_tool/native/wechatdb_broker
/src/wechat_decrypt_tool/native/wechatdb_native_build.json
/src/wechat_decrypt_tool/native/macos/db-key/
Expand Down
9 changes: 9 additions & 0 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"smoke:win:real": "node scripts/smoke-windows-real-database.cjs",
"dist": "npm run dist:win",
"dist:win": "npm run build:ui && npm run build:backend && npm run build:icon && electron-builder --win --x64 --publish never",
"dist:linux": "npm run build:ui && npm run build:backend && electron-builder --linux dir --x64 --publish never && node scripts/build-linux-installer.cjs",
"dist:mac": "npm run dist:mac:arm64",
"dist:mac:arm64": "npm run verify:mac:native && npm run build:ui && npm run build:backend && npm run build:icon && electron-builder --mac dmg zip --arm64 --publish never",
"dist:mac:arm64:release": "npm run verify:mac:native && npm run build:ui && npm run build:backend && npm run build:icon && cross-env MACOS_DISTRIBUTION_BUILD=1 electron-builder --mac dmg zip --arm64 --publish never --config.forceCodeSigning=true"
Expand Down Expand Up @@ -101,6 +102,14 @@
]
}
],
"linux": {
"icon": "src/icon.png",
"category": "Utility",
"executableName": "wechat-data-analysis",
"target": [
"dir"
]
},
"win": {
"icon": "build/icon.ico",
"forceCodeSigning": true,
Expand Down
35 changes: 33 additions & 2 deletions desktop/scripts/build-backend.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { aiPackagingArgs, runPackagedAiSmoke } = require('./ai-packaging.cjs');
const crypto = require("crypto");
const fs = require("fs");
const os = require("os");
const path = require("path");
Expand All @@ -11,6 +12,10 @@ const {
macosNativeManifestErrors,
resolveMacosNativeCoreArtifacts,
} = require("./macos-native-core-packaging.cjs");
const {
linuxNativeManifestErrors,
resolveLinuxNativeCoreArtifacts,
} = require("./linux-native-core-packaging.cjs");
const {
resolveIntegrityNativeArtifact,
} = require("./integrity-native-packaging.cjs");
Expand Down Expand Up @@ -40,6 +45,8 @@ const NATIVE_CORE_MANIFEST = "wechatdb_native_build.json";
const NATIVE_CORE_ARTIFACTS = Object.freeze({
win32: ["wechatdb_client.dll", "wechatdb_broker.exe", NATIVE_CORE_MANIFEST],
darwin: ["libwechatdb_client.dylib", "wechatdb_broker", NATIVE_CORE_MANIFEST],
// Linux 与 macOS 共用同名 broker,客户端是 ELF 共享库;身份靠内容哈希而不是代码签名。
linux: ["libwechatdb_client.so", "wechatdb_broker", NATIVE_CORE_MANIFEST],
});
const NATIVE_CORE_FILE_NAMES = new Set(Object.values(NATIVE_CORE_ARTIFACTS).flat());
const LEGACY_WCDB_FILE_NAMES = new Set([
Expand Down Expand Up @@ -90,12 +97,15 @@ function nativeCoreManifestErrors(manifest) {
if (!manifest || Array.isArray(manifest) || typeof manifest !== "object") {
return ["manifest must be a JSON object"];
}
if (!new Set([2, 3]).has(manifest.schemaVersion)) {
errors.push("schemaVersion must equal 2 or 3");
if (!new Set([2, 3, 4]).has(manifest.schemaVersion)) {
errors.push("schemaVersion must equal 2, 3 or 4");
}
if (manifest.schemaVersion === 3 && manifest.platform !== "macos") {
errors.push("schemaVersion 3 requires platform macos");
}
if (manifest.schemaVersion === 4 && manifest.platform !== "linux") {
errors.push("schemaVersion 4 requires platform linux");
}
if (manifest.schemaVersion === 2 && Object.prototype.hasOwnProperty.call(manifest, "platform")) {
errors.push("schemaVersion 2 must not declare platform");
}
Expand Down Expand Up @@ -145,6 +155,10 @@ function nativeCoreProductionManifestErrors(
if (manifest?.schemaVersion === 3) {
return macosNativeManifestErrors(manifest, { nowUnix });
}
// schema v4 是 Linux 的完整契约(含内容哈希 pin 与 45 天窗口),不能走下面 Windows 那套。
if (manifest?.schemaVersion === 4) {
return linuxNativeManifestErrors(manifest, { nowUnix });
}
const errors = nativeCoreManifestErrors(manifest);
const buildIssuedAtUnix = manifest?.buildIssuedAtUnix;
const buildExpiresAtUnix = manifest?.buildExpiresAtUnix;
Expand Down Expand Up @@ -271,6 +285,11 @@ function resolveNativeCoreArtifacts({ env = process.env, platform = process.plat
return { ...resolved, allowDevelopment: false, required: true };
}

if (platform === "linux" && !allowDevelopment) {
const resolved = resolveLinuxNativeCoreArtifacts({ env, platform });
return { ...resolved, allowDevelopment: false, required: true };
}

const artifactDir = path.resolve(explicitValue);
let directoryStat;
try {
Expand Down Expand Up @@ -370,13 +389,25 @@ function buildIntegrityNativeBinary({ env = process.env, platform = process.plat
}
const integrityTargetDir = path.join(repoRoot, "native", "wce_integrity", "target", "release");
const fileName = platform === "darwin" ? "libwce_integrity.dylib" : "libwce_integrity.so";
// 构建密钥 = 编译 wce_integrity 时注入的 P-256 私钥(WCE_SIGNING_KEY_HEX),只用来给导出物封签,
// 公钥随模块一起编译进去,没有任何外部预注册,所以「每次构建现生成一把」是安全的。
// 这与 Windows 官方入口 tools/build_wce_integrity.ps1 -GenerateEphemeralSigningKey 语义一致:
// 有注入就用注入的(可复现),没注入就现生成一把临时的(Linux/macOS 本地构建的默认)。
const providedSigningKey = String(env.WCE_SIGNING_KEY_HEX || "").trim();
const signingKeyHex = providedSigningKey || crypto.randomBytes(32).toString("hex");
if (!providedSigningKey) {
process.stdout.write(
`wce_integrity: generated an ephemeral build signing key for ${platform} (set WCE_SIGNING_KEY_HEX to pin it)\n`
);
}
const result = spawnSync(
"cargo",
["build", "--manifest-path", integrityManifest, "--release"],
{
cwd: repoRoot,
env: {
...env,
WCE_SIGNING_KEY_HEX: signingKeyHex,
WCE_UI_PUBLIC_DIR: path.join(repoRoot, "frontend", ".output", "public"),
},
stdio: "inherit",
Expand Down
Loading
Loading