From 95abb8b6676c2d672c1540fbc0b9bf6b4ae1ef30 Mon Sep 17 00:00:00 2001 From: Chaim Date: Mon, 11 May 2026 16:37:32 +0000 Subject: [PATCH] ci: unify publish-clickonce into single transcripted step + upload logs Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/build.yml | 245 +++++++++++++++++++------------------ 1 file changed, 125 insertions(+), 120 deletions(-) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index f940e2e..b49d23f 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -205,143 +205,148 @@ jobs: "versionUscore=$($version -replace '\.','_')" | Out-File -Append $env:GITHUB_OUTPUT Write-Host "Version: $version" - - name: Ensure code-signing cert in CurrentUser\My AND LocalMachine\My + - name: Publish ClickOnce (single step with transcript) shell: powershell env: - THUMB: ${{ secrets.CODESIGN_THUMBPRINT }} - PFX_B64: ${{ secrets.CODESIGN_CERT_PFX_BASE64 }} - PFX_PWD: ${{ secrets.CODESIGN_CERT_PASSWORD }} + THUMB: ${{ secrets.CODESIGN_THUMBPRINT }} + PFX_B64: ${{ secrets.CODESIGN_CERT_PFX_BASE64 }} + PFX_PWD: ${{ secrets.CODESIGN_CERT_PASSWORD }} + MAGE: ${{ env.MAGE }} + SIGNTOOL: ${{ env.SIGNTOOL }} + VERSION: ${{ steps.ver.outputs.version }} + VERSION_USCORE: ${{ steps.ver.outputs.versionUscore }} run: | - $thumb = $env:THUMB - # Import to CurrentUser\My (where VSTO ResolveKeySource looks). - $inUser = Get-ChildItem Cert:\CurrentUser\My -ErrorAction SilentlyContinue | - Where-Object { $_.Thumbprint -eq $thumb } - if (-not $inUser) { - $pfxBytes = [Convert]::FromBase64String($env:PFX_B64) - $tmp = Join-Path $env:TEMP "cs-$([Guid]::NewGuid()).pfx" - [IO.File]::WriteAllBytes($tmp, $pfxBytes) - $pwd = ConvertTo-SecureString -String $env:PFX_PWD -Force -AsPlainText - Import-PfxCertificate -FilePath $tmp -CertStoreLocation Cert:\CurrentUser\My -Password $pwd | Out-Null - Remove-Item -Force $tmp - Write-Host "Imported into CurrentUser\My." - } else { Write-Host "Already in CurrentUser\My." } + $ErrorActionPreference = 'Continue' + Start-Transcript -Path publish.console.log -IncludeInvocationHeader -Force + $code = 0 + try { + $thumb = $env:THUMB - $found = Get-ChildItem Cert:\LocalMachine\My -ErrorAction SilentlyContinue | - Where-Object { $_.Thumbprint -eq $thumb } - if ($found) { - Write-Host "Cert $thumb already in LocalMachine\My." - return + Write-Host "=== STEP A: cert into stores ===" + $inUser = Get-ChildItem Cert:\CurrentUser\My -EA SilentlyContinue | ? { $_.Thumbprint -eq $thumb } + if (-not $inUser) { + $b = [Convert]::FromBase64String($env:PFX_B64); $t = Join-Path $env:TEMP "u.pfx" + [IO.File]::WriteAllBytes($t, $b) + $p = ConvertTo-SecureString $env:PFX_PWD -Force -AsPlainText + Import-PfxCertificate -FilePath $t -CertStoreLocation Cert:\CurrentUser\My -Password $p | Out-Null + Remove-Item -Force $t + } + Write-Host " CurrentUser\My ok" + $inMach = Get-ChildItem Cert:\LocalMachine\My -EA SilentlyContinue | ? { $_.Thumbprint -eq $thumb } + if (-not $inMach) { + $b = [Convert]::FromBase64String($env:PFX_B64); $t = Join-Path $env:TEMP "m.pfx" + [IO.File]::WriteAllBytes($t, $b) + $p = ConvertTo-SecureString $env:PFX_PWD -Force -AsPlainText + Import-PfxCertificate -FilePath $t -CertStoreLocation Cert:\LocalMachine\My -Password $p | Out-Null + Remove-Item -Force $t + } + Write-Host " LocalMachine\My ok" + + Write-Host "=== STEP B: patch csproj ===" + $csproj = 'src/OutlookAddin/OutlookAddin.csproj' + $cnt = Get-Content $csproj -Raw + $new = $cnt -replace [regex]::Escape('2399E9BF73820F3B1FC744BCBFA7F82D536E3256'), $thumb + if ($new -ne $cnt) { Set-Content $csproj -Value $new -NoNewline; Write-Host "patched" } + else { Write-Host "already patched" } + + Write-Host "=== STEP C: msbuild /restore /t:Build ===" + $msbuildArgs = @( + 'OutlookAddin.sln','/restore','/t:Build', + '/p:Configuration=Release','/p:Platform="Any CPU"','/m', + '/flp:logfile=publish-msbuild.log;verbosity=detailed' + ) + & msbuild @msbuildArgs + if ($LASTEXITCODE -ne 0) { throw "msbuild failed with $LASTEXITCODE" } + + Write-Host "=== STEP D: signtool sign assemblies ===" + & "$env:SIGNTOOL" sign /sha1 $thumb /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 ` + src/OutlookAddin/bin/Release/MarcusLaw.OutlookAddin.dll ` + src/OutlookAddin.Core/bin/Release/net48/MarcusLaw.OutlookAddin.Core.dll ` + src/OutlookAddin.UI/bin/Release/net48/MarcusLaw.OutlookAddin.UI.dll ` + src/OutlookAddinProtocolHandler/bin/Release/net48/OutlookAddinProtocolHandler.exe + if ($LASTEXITCODE -ne 0) { throw "signtool failed with $LASTEXITCODE" } + + Write-Host "=== STEP E: mage publish ClickOnce ===" + $version = $env:VERSION + $versionUscore = $env:VERSION_USCORE + $appBin = "src/OutlookAddin/bin/Release" + $publishDir = "publish" + $appFilesDir = "$publishDir/Application Files/OutlookAddin_$versionUscore" + New-Item -ItemType Directory -Force -Path $appFilesDir | Out-Null + Copy-Item -Recurse -Force "$appBin/*" $appFilesDir + + & "$env:MAGE" -New Application -ToFile "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" ` + -Name "Marcus-Law OutlookAddin" -Version $version -FromDirectory $appFilesDir -TrustLevel FullTrust + if ($LASTEXITCODE -ne 0) { throw "mage -New failed" } + + & "$env:MAGE" -Sign "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" -CertHash $thumb -TimeStampUri http://timestamp.digicert.com + if ($LASTEXITCODE -ne 0) { throw "mage -Sign app manifest failed" } + + Copy-Item "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" "$publishDir/OutlookAddin.vsto" + & "$env:MAGE" -Update "$publishDir/OutlookAddin.vsto" ` + -AppManifest "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" ` + -ProviderUrl "$env:PROVIDER_URL" -Publisher "Marcus-Law" -Install true + if ($LASTEXITCODE -ne 0) { throw "mage -Update deployment manifest failed" } + + & "$env:MAGE" -Sign "$publishDir/OutlookAddin.vsto" -CertHash $thumb -TimeStampUri http://timestamp.digicert.com + if ($LASTEXITCODE -ne 0) { throw "mage -Sign deployment manifest failed" } + + Get-ChildItem -Recurse -File "$appFilesDir" | + ? { $_.Extension -notin '.manifest','.deploy' } | + % { Rename-Item -LiteralPath $_.FullName -NewName ($_.Name + '.deploy') } + + Write-Host "=== STEP F: stage protocol handler ===" + $stagingDir = "publish/protocol-handler" + New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null + Copy-Item -Recurse -Force "src/OutlookAddinProtocolHandler/bin/Release/net48/*" $stagingDir + Copy-Item -Force "tools/install-protocol.ps1" "$stagingDir/install-protocol.ps1" + Copy-Item -Force "tools/uninstall-protocol.ps1" "$stagingDir/uninstall-protocol.ps1" + + Write-Host "=== STEP G: pack + upload publish.tar.gz ===" + tar -czf publish.tar.gz publish + $pair = "$($env:REG_USER):$($env:REG_PASS)" + } catch { + Write-Host "FATAL: $($_.Exception.Message)" + Write-Host $_.ScriptStackTrace + $code = 1 + } finally { + Stop-Transcript } - Write-Warning "Cert $thumb missing from LocalMachine\My. Importing from CODESIGN_CERT_PFX_BASE64." - $pfxBytes = [Convert]::FromBase64String($env:PFX_B64) - $tmpPfx = Join-Path $env:TEMP "codesign-$([Guid]::NewGuid()).pfx" - [IO.File]::WriteAllBytes($tmpPfx, $pfxBytes) - $securePwd = ConvertTo-SecureString -String $env:PFX_PWD -Force -AsPlainText - Import-PfxCertificate -FilePath $tmpPfx ` - -CertStoreLocation Cert:\LocalMachine\My ` - -Password $securePwd | Out-Null - Remove-Item -Force $tmpPfx - Write-Host "Cert imported." + if ($code -ne 0) { exit $code } - - name: Patch csproj to use real cert thumbprint - shell: powershell - env: - THUMB: ${{ secrets.CODESIGN_THUMBPRINT }} - run: | - $csproj = 'src/OutlookAddin/OutlookAddin.csproj' - $orig = '2399E9BF73820F3B1FC744BCBFA7F82D536E3256' - $content = Get-Content $csproj -Raw - $patched = $content -replace [regex]::Escape($orig), $env:THUMB - if ($patched -ne $content) { Set-Content -Path $csproj -Value $patched -NoNewline; Write-Host "Patched csproj." } - else { Write-Warning "Original thumbprint not present in csproj." } - - - name: Build Release (restore + compile) - run: msbuild OutlookAddin.sln /restore /t:Build /p:Configuration=Release /p:Platform="Any CPU" /m - - # Code signing -- cert in LocalMachine\My (or CurrentUser\My); signtool - # picks it by thumbprint, the service runs as LocalSystem so the runtime - # search reaches both stores. - - name: Sign assemblies - shell: powershell - run: | - $thumb = "${{ secrets.CODESIGN_THUMBPRINT }}" - & "$env:SIGNTOOL" sign /sha1 $thumb /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 ` - src/OutlookAddin/bin/Release/MarcusLaw.OutlookAddin.dll ` - src/OutlookAddin.Core/bin/Release/net48/MarcusLaw.OutlookAddin.Core.dll ` - src/OutlookAddin.UI/bin/Release/net48/MarcusLaw.OutlookAddin.UI.dll ` - src/OutlookAddinProtocolHandler/bin/Release/net48/OutlookAddinProtocolHandler.exe - - - name: Publish ClickOnce - shell: powershell - run: | - $version = "${{ steps.ver.outputs.version }}" - $versionUscore = "${{ steps.ver.outputs.versionUscore }}" - $thumb = "${{ secrets.CODESIGN_THUMBPRINT }}" - - $appBin = "src/OutlookAddin/bin/Release" - $publishDir = "publish" - $appFilesDir = "$publishDir/Application Files/OutlookAddin_$versionUscore" - - New-Item -ItemType Directory -Force -Path $appFilesDir | Out-Null - Copy-Item -Recurse -Force "$appBin/*" $appFilesDir - - # Application manifest (.dll.manifest) - & "$env:MAGE" -New Application ` - -ToFile "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" ` - -Name "Marcus-Law OutlookAddin" ` - -Version $version ` - -FromDirectory $appFilesDir ` - -TrustLevel FullTrust - - & "$env:MAGE" -Sign "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" ` - -CertHash $thumb ` - -TimeStampUri http://timestamp.digicert.com - - # Deployment manifest (.vsto) - Copy-Item "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" "$publishDir/OutlookAddin.vsto" - & "$env:MAGE" -Update "$publishDir/OutlookAddin.vsto" ` - -AppManifest "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" ` - -ProviderUrl "$env:PROVIDER_URL" ` - -Publisher "Marcus-Law" ` - -Install true - - & "$env:MAGE" -Sign "$publishDir/OutlookAddin.vsto" ` - -CertHash $thumb ` - -TimeStampUri http://timestamp.digicert.com - - # Rename payload files to .deploy (ClickOnce convention). - Get-ChildItem -Recurse -File "$appFilesDir" | - Where-Object { $_.Extension -notin '.manifest','.deploy' } | - ForEach-Object { Rename-Item -LiteralPath $_.FullName -NewName ($_.Name + '.deploy') } - - - name: Stage protocol handler - shell: powershell - run: | - $stagingDir = "publish/protocol-handler" - New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null - Copy-Item -Recurse -Force "src/OutlookAddinProtocolHandler/bin/Release/net48/*" $stagingDir - Copy-Item -Force "tools/install-protocol.ps1" "$stagingDir/install-protocol.ps1" - Copy-Item -Force "tools/uninstall-protocol.ps1" "$stagingDir/uninstall-protocol.ps1" - - - name: Pack publish/ and upload to Gitea generic package registry - # actions/upload-artifact@v4 is broken on this self-hosted runner, so - # we tar+zstd the publish dir and PUT it to the generic package - # registry. dockerize-and-deploy then fetches it via GET. + - name: Upload publish.tar.gz to generic package + if: success() shell: powershell env: REG_USER: ${{ secrets.REGISTRY_USER }} REG_PASS: ${{ secrets.REGISTRY_PASSWORD }} VER: ${{ steps.ver.outputs.versionDot }} run: | - tar -czf publish.tar.gz publish $pair = "$($env:REG_USER):$($env:REG_PASS)" $auth = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair)) $uri = "https://gitea.dev.marcus-law.co.il/api/packages/espocrm-extensions/generic/outlook-addin-publish/$($env:VER)/publish.tar.gz" - Write-Host "PUT $uri" - Invoke-WebRequest -Method Put -Uri $uri -Headers @{ Authorization = $auth } ` - -InFile publish.tar.gz -ContentType 'application/gzip' -UseBasicParsing | Out-Null + Invoke-WebRequest -Method Put -Uri $uri -Headers @{ Authorization = $auth } -InFile publish.tar.gz -ContentType 'application/gzip' -UseBasicParsing | Out-Null Write-Host "Uploaded $((Get-Item publish.tar.gz).Length) bytes." + - name: Push publish logs to generic package + if: always() + shell: powershell + env: + REG_USER: ${{ secrets.REGISTRY_USER }} + REG_PASS: ${{ secrets.REGISTRY_PASSWORD }} + RUN_ID: ${{ github.run_id }} + run: | + $pair = "$($env:REG_USER):$($env:REG_PASS)" + $auth = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair)) + $base = "https://gitea.dev.marcus-law.co.il/api/packages/espocrm-extensions/generic/outlook-addin-publish-log/run-$env:RUN_ID" + foreach ($f in @('publish.console.log','publish-msbuild.log','src/OutlookAddin/OutlookAddin.csproj')) { + if (Test-Path $f) { + $name = (Split-Path $f -Leaf) + Write-Host "PUT $base/$name" + try { Invoke-WebRequest -Method Put -Uri "$base/$name" -Headers @{ Authorization = $auth } -InFile $f -ContentType 'text/plain' -UseBasicParsing | Out-Null } catch { Write-Host " failed: $($_.Exception.Message)" } + } + } + dockerize-and-deploy: needs: publish-clickonce runs-on: ubuntu-latest