From cbb29fa8429f6f81a865390a4dee8d21da462301 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 5 Dec 2025 07:34:30 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20ci:=20add=20Windows=20build?= =?UTF-8?q?=20to=20PR/merge=20queue=20and=20EV=20code=20signing=20to=20rel?= =?UTF-8?q?eases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Windows build job to build.yml (runs on PRs and merge queue) - Add Windows EV code signing with GCP KMS and jsign (mirrors coder-desktop-windows pattern) - Custom signing script at scripts/sign-windows.js for electron-builder - Uses repository variables for non-sensitive config (EV_KEYSTORE, EV_KEY, EV_TSA_URL) - Uses secrets for sensitive data (EV_SIGNING_CERT, GCP_WORKLOAD_ID_PROVIDER, GCP_SERVICE_ACCOUNT) - Gracefully skips signing if secrets not configured _Generated with `mux`_ --- .github/workflows/build.yml | 36 ++++++++++++++++ .github/workflows/release.yml | 44 ++++++++++++++++++++ docs/system-prompt.md | 1 - package.json | 6 ++- scripts/sign-windows.js | 78 +++++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 scripts/sign-windows.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b3a3e33d..d54c31d24 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,6 +64,42 @@ jobs: retention-days: 30 if-no-files-found: error + build-windows: + name: Build Windows + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for git describe to find tags + + - uses: ./.github/actions/setup-mux + + - name: Install GNU Make (for build) + run: choco install -y make + + - name: Verify tools + shell: bash + run: | + make --version + bun --version + magick --version | head -1 + + - name: Build application + run: bun run build + + # No code signing - releases use release.yml (triggered by tag publish). + - name: Package for Windows + run: make dist-win + + - name: Upload Windows exe + uses: actions/upload-artifact@v4 + with: + name: windows-exe + path: release/*.exe + retention-days: 30 + if-no-files-found: error + build-vscode-extension: name: Build VS Code Extension runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c05401b04..b1ae79acc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ on: permissions: contents: write # Required for electron-builder to upload release assets + id-token: write # Required for GCP workload identity authentication (Windows code signing) env: RELEASE_TAG: ${{ inputs.tag || github.event.release.tag_name || github.ref_name }} @@ -168,7 +169,50 @@ jobs: - name: Build application run: bun run build + # Setup Java for jsign (EV code signing with GCP KMS) + - name: Setup Java + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4.7.0 + with: + distribution: "zulu" + java-version: "11.0" + + - name: Authenticate to Google Cloud + id: gcloud_auth + if: ${{ vars.GCP_WORKLOAD_ID_PROVIDER != '' }} + uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935 # v2.1.8 + with: + workload_identity_provider: ${{ vars.GCP_WORKLOAD_ID_PROVIDER }} + service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} + token_format: "access_token" + + - name: Setup code signing + shell: pwsh + run: | + if (-not $env:EV_SIGNING_CERT) { + Write-Host "⚠️ No Windows code signing certificate provided - building unsigned" + exit 0 + } + + # Save EV certificate to temp file + $certPath = Join-Path $env:TEMP "ev_cert.pem" + Set-Content -Path $certPath -Value $env:EV_SIGNING_CERT + Add-Content -Path $env:GITHUB_ENV -Value "EV_CERTIFICATE_PATH=$certPath" + + # Download jsign + $jsignPath = Join-Path $env:TEMP "jsign-6.0.jar" + Invoke-WebRequest -Uri "https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar" -OutFile $jsignPath + Add-Content -Path $env:GITHUB_ENV -Value "JSIGN_PATH=$jsignPath" + + Write-Host "✅ Windows EV code signing configured" + env: + EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }} + - name: Package and publish for Windows (.exe) run: bun x electron-builder --win --publish always env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # EV signing environment variables (used by custom sign script if configured) + EV_KEYSTORE: ${{ vars.EV_KEYSTORE }} + EV_KEY: ${{ vars.EV_KEY }} + EV_TSA_URL: ${{ vars.EV_TSA_URL }} + GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} diff --git a/docs/system-prompt.md b/docs/system-prompt.md index fb5bb1305..3b7de1ba0 100644 --- a/docs/system-prompt.md +++ b/docs/system-prompt.md @@ -62,5 +62,4 @@ You are in a git worktree at ${workspacePath} } ``` - {/* END SYSTEM_PROMPT_DOCS */} diff --git a/package.json b/package.json index 375273b2d..e8f04a5b5 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,11 @@ "win": { "target": "nsis", "icon": "build/icon.png", - "artifactName": "${productName}-${version}-${arch}.${ext}" + "artifactName": "${productName}-${version}-${arch}.${ext}", + "sign": "scripts/sign-windows.js", + "signingHashAlgorithms": [ + "sha256" + ] }, "npmRebuild": false, "buildDependenciesFromSource": false diff --git a/scripts/sign-windows.js b/scripts/sign-windows.js new file mode 100644 index 000000000..6e1b5ebc5 --- /dev/null +++ b/scripts/sign-windows.js @@ -0,0 +1,78 @@ +/** + * Windows EV code signing script for electron-builder + * Uses jsign with GCP Cloud KMS for EV certificate signing + * + * Required environment variables: + * JSIGN_PATH - Path to jsign JAR file + * EV_KEYSTORE - GCP Cloud KMS keystore URL + * EV_KEY - Key alias in the keystore + * EV_CERTIFICATE_PATH - Path to the EV certificate PEM file + * EV_TSA_URL - Timestamp server URL + * GCLOUD_ACCESS_TOKEN - GCP access token for authentication + */ + +const { execSync } = require("child_process"); +const path = require("path"); + +/** + * @param {import("electron-builder").CustomWindowsSignTaskConfiguration} configuration + * @returns {Promise} + */ +exports.default = async function sign(configuration) { + const filePath = configuration.path; + + // Check if signing is configured + if (!process.env.JSIGN_PATH || !process.env.EV_KEYSTORE) { + console.log( + `⚠️ Windows code signing not configured - skipping signing for ${filePath}` + ); + return; + } + + // Validate required environment variables + const requiredVars = [ + "JSIGN_PATH", + "EV_KEYSTORE", + "EV_KEY", + "EV_CERTIFICATE_PATH", + "EV_TSA_URL", + "GCLOUD_ACCESS_TOKEN", + ]; + + for (const varName of requiredVars) { + if (!process.env[varName]) { + throw new Error(`Missing required environment variable: ${varName}`); + } + } + + console.log(`Signing ${filePath} with EV certificate...`); + + const jsignArgs = [ + "-jar", + process.env.JSIGN_PATH, + "--storetype", + "GOOGLECLOUD", + "--storepass", + process.env.GCLOUD_ACCESS_TOKEN, + "--keystore", + process.env.EV_KEYSTORE, + "--alias", + process.env.EV_KEY, + "--certfile", + process.env.EV_CERTIFICATE_PATH, + "--tsmode", + "RFC3161", + "--tsaurl", + process.env.EV_TSA_URL, + filePath, + ]; + + try { + execSync(`java ${jsignArgs.map((a) => `"${a}"`).join(" ")}`, { + stdio: "inherit", + }); + console.log(`✅ Successfully signed ${filePath}`); + } catch (error) { + throw new Error(`Failed to sign ${filePath}: ${error.message}`); + } +}; From ac6d3253e8d72c1a919f72f60fd1e1911f296efb Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:31:53 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A4=96=20refactor:=20put=20runtime=20?= =?UTF-8?q?selector=20and=20name=20on=20same=20row=20in=20create=20workspa?= =?UTF-8?q?ce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move RuntimeIconSelector and Name input to first row - Use flex-wrap for responsive layout on smaller screens - Conditionally render second row only when branch/SSH controls needed --- .../components/ChatInput/CreationControls.tsx | 169 +++++++++--------- 1 file changed, 87 insertions(+), 82 deletions(-) diff --git a/src/browser/components/ChatInput/CreationControls.tsx b/src/browser/components/ChatInput/CreationControls.tsx index 00c99dcc7..deef7bbab 100644 --- a/src/browser/components/ChatInput/CreationControls.tsx +++ b/src/browser/components/ChatInput/CreationControls.tsx @@ -56,59 +56,7 @@ export function CreationControls(props: CreationControlsProps) { return (
- {/* First row: Workspace name with magic wand toggle */} -
- -
- - {/* Magic wand / loading indicator - vertically centered */} -
- {nameState.isGenerating ? ( - - ) : ( - - - - {nameState.autoGenerate ? "Auto-naming enabled" : "Click to enable auto-naming"} - - - )} -
-
- {/* Error display - inline */} - {nameState.error && {nameState.error}} -
- - {/* Second row: Runtime, Branch, SSH */} + {/* First row: Runtime selector (left) + Workspace name (right), wraps on small screens */}
{/* Runtime Selector - icon-based with tooltips */} - {/* Trunk Branch Selector - hidden for Local runtime */} - {showTrunkBranchSelector && ( -
- - + {/* Magic wand / loading indicator - vertically centered */} +
+ {nameState.isGenerating ? ( + + ) : ( + + + + {nameState.autoGenerate ? "Auto-naming enabled" : "Click to enable auto-naming"} + + + )} +
- )} - - {/* SSH Host Input - after From selector */} - {props.runtimeMode === RUNTIME_MODE.SSH && ( - props.onSshHostChange(e.target.value)} - placeholder="user@host" - disabled={props.disabled} - className="bg-separator text-foreground border-border-medium focus:border-accent h-6 w-32 rounded border px-1 text-xs focus:outline-none disabled:opacity-50" - /> - )} + {/* Error display - inline */} + {nameState.error && {nameState.error}} +
+ + {/* Second row: Branch, SSH - only shown when there's content */} + {(showTrunkBranchSelector || props.runtimeMode === RUNTIME_MODE.SSH) && ( +
+ {/* Trunk Branch Selector - hidden for Local runtime */} + {showTrunkBranchSelector && ( +
+ + props.onSshHostChange(e.target.value)} + placeholder="user@host" + disabled={props.disabled} + className="bg-separator text-foreground border-border-medium focus:border-accent h-6 w-32 rounded border px-1 text-xs focus:outline-none disabled:opacity-50" + /> + )} +
+ )}
); } From a0af431ac2575fb6753a3c5f3b42a9186a32ba6c Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Fri, 5 Dec 2025 16:34:43 +0000 Subject: [PATCH 3/3] fix: format --- src/browser/components/ChatInput/CreationControls.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/browser/components/ChatInput/CreationControls.tsx b/src/browser/components/ChatInput/CreationControls.tsx index deef7bbab..d585bfc6e 100644 --- a/src/browser/components/ChatInput/CreationControls.tsx +++ b/src/browser/components/ChatInput/CreationControls.tsx @@ -97,7 +97,9 @@ export function CreationControls(props: CreationControlsProps) { onClick={handleWandClick} disabled={props.disabled} className="flex h-full items-center disabled:opacity-50" - aria-label={nameState.autoGenerate ? "Disable auto-naming" : "Enable auto-naming"} + aria-label={ + nameState.autoGenerate ? "Disable auto-naming" : "Enable auto-naming" + } >