From 781cf30c55f55425f203ecc6ee0c1466378f96a7 Mon Sep 17 00:00:00 2001 From: PiEgg Date: Tue, 10 Feb 2026 10:25:53 +0800 Subject: [PATCH] :sparkles: Feature: windows signature (#1386) --- .github/workflows/main.yml | 60 +++++++++++++++++++++++++++-- scripts/update-win-yaml.js | 79 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 scripts/update-win-yaml.js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 757ba94..781cef4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,11 +26,19 @@ on: required: true default: false type: boolean - skip_notarize: - description: "Skip Notarization (true/false)" + skip_mac_notarize: + description: "Skip Mac Notarization (true/false)" required: true default: false type: boolean + win_signing_mode: + description: "Windows Signing Mode: release-signing(default) or test-signing" + required: true + default: "release-signing" + type: choice + options: + - release-signing + - test-signing env: NODE_VERSION: 22.x @@ -82,7 +90,7 @@ jobs: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - SKIP_NOTARIZE: ${{ inputs.skip_notarize }} + SKIP_NOTARIZE: ${{ inputs.skip_mac_notarize }} - name: Upload Artifact uses: actions/upload-artifact@v4 @@ -138,11 +146,57 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + # 1. Build (Unsigned) - name: Build Windows App (${{ matrix.arch }}) run: pnpm run build && pnpm exec electron-builder --config electron-builder.config.ts --win ${{matrix.target}} ${{ matrix.build_arch }} --publish never env: GH_TOKEN: ${{ secrets.GH_TOKEN }} + # 2. Upload Unsigned Artifact to SignPath (Intermediate Step) + - name: Upload Unsigned Artifact for Signing + id: upload-unsigned + uses: actions/upload-artifact@v4 + with: + name: unsigned-${{ matrix.arch }} + path: dist/*.exe + retention-days: 1 + if-no-files-found: error + + # 3. Submit to SignPath and Wait + - name: Sign Artifact with SignPath + uses: signpath/github-action-submit-signing-request@v2 + env: + SIGNPATH_SIGNING_POLICY_SLUG: | + ${{ (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') || inputs.win_signing_mode == 'release-signing') + && 'release-signing' + || 'test-signing' }} + ARTIFACT_SLUG: | + ${{ (matrix.arch == 'x64-ia32') + && 'PicGo-Windows' + || (matrix.arch == 'arm64') + && 'PicGo-Windows-ARM64' + || '' }} + with: + api-token: "${{ secrets.SIGNPATH_API_TOKEN }}" + organization-id: "${{ secrets.SIGNPATH_ORGANIZATION_ID }}" + project-slug: "${{ secrets.SIGNPATH_PROJECT_SLUG }}" + signing-policy-slug: "${{ env.SIGNPATH_SIGNING_POLICY_SLUG }}" + github-artifact-id: "${{ steps.upload-unsigned.outputs.artifact-id }}" + artifact-configuration-slug: "${{ env.ARTIFACT_SLUG }}" + wait-for-completion: true + output-artifact-directory: "signed-artifact" + + # 4. Replace Unsigned with Signed & Fix Blockmap (Critical for Auto-Update) + - name: Replace Unsigned with Signed & Update latest.yml + shell: powershell + run: | + # Move signed artifacts to dist folder, overwriting existing ones + Move-Item -Path "signed-artifact\*.exe" -Destination "dist\" -Force + Write-Host "✅ Signed artifacts moved to dist folder." + + # Run the Node.js script to update latest.yml + node scripts/update-win-yaml.js + - name: Upload Artifact uses: actions/upload-artifact@v4 with: diff --git a/scripts/update-win-yaml.js b/scripts/update-win-yaml.js new file mode 100644 index 0000000..da5b6e4 --- /dev/null +++ b/scripts/update-win-yaml.js @@ -0,0 +1,79 @@ +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') +const yaml = require('js-yaml') + +const distDir = path.join(__dirname, '../dist') +const yamlPath = path.join(distDir, 'latest.yml') + +if (!fs.existsSync(yamlPath)) { + console.log('⚠️ latest.yml not found in dist/. Skipping update.') + process.exit(0) +} + +console.log(`Reading ${yamlPath}...`) +const yamlContent = fs.readFileSync(yamlPath, 'utf8') +let doc + +try { + doc = yaml.load(yamlContent) +} catch (e) { + console.error('❌ Failed to parse latest.yml:', e) + process.exit(1) +} + +// Get all .exe files in dist directory +const files = fs.readdirSync(distDir).filter(f => f.endsWith('.exe')) + +if (files.length === 0) { + console.log('⚠️ No .exe files found in dist/.') + process.exit(0) +} + +let updated = false + +files.forEach(file => { + const filePath = path.join(distDir, file) + + // 1. Calculate new Hash and Size + const buffer = fs.readFileSync(filePath) + const hash = crypto.createHash('sha512').update(buffer).digest('base64') + const size = fs.statSync(filePath).size + + console.log(`Processing ${file}:`) + console.log(` -> New Hash: ${hash}`) + console.log(` -> New Size: ${size}`) + + // 2. Update entries in 'files' list + if (Array.isArray(doc.files)) { + const fileEntry = doc.files.find(f => f.url === file) + if (fileEntry) { + fileEntry.sha512 = hash + fileEntry.size = size + updated = true + console.log(' -> Updated file entry in yaml object') + } + } + + // 3. Update root path entry (if exists) + // electron-builder's latest.yml usually has root path, sha512, size + // corresponding to the main file of current build (usually x64 or current arch) + if (doc.path === file) { + doc.sha512 = hash + doc.size = size + updated = true + console.log(' -> Updated root path entry in yaml object') + } +}) + +if (updated) { + // 4. Dump back to file + // lineWidth: -1 prevents long strings from wrapping, keeping it clean + const newYamlContent = yaml.dump(doc, { lineWidth: -1 }) + fs.writeFileSync(yamlPath, newYamlContent, 'utf8') + console.log('✅ latest.yml updated successfully.') + console.log('New yaml content:') + console.log(newYamlContent) +} else { + console.log('⚠️ No matching entries found in latest.yml to update.') +} \ No newline at end of file