This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OutlookAddin/.gitea/workflows/build.yml
T
chaim 73d6799eb2 feat(ci): CI/CD container delivery for ClickOnce artifacts
Replace direct SSH rsync with build → push to registry → Coolify pull pattern,
matching the rest of the infra (decisions-court, ai-gateway, ...). Adds cdn/
Dockerfile + nginx.conf to serve .vsto/.application/.manifest/.deploy with
correct MIME types behind a StripPrefix Traefik middleware.

The new workflow has three jobs:
- build:               Windows runner, nuget restore + msbuild + dotnet test.
- publish-clickonce:   Windows runner, only on v* tag — sign assemblies +
                       mage manifests + upload publish/ as artifact.
- dockerize-and-deploy: Linux runner — download artifact, build & push
                       outlook-addin-cdn:<version> + :latest to
                       gitea.dev.marcus-law.co.il, then trigger Coolify
                       redeploy of app awdlvjlozz0dvn6kjlme9rd7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:10:39 +00:00

248 lines
9.6 KiB
YAML

name: Build and deploy OutlookAddin
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: gitea.dev.marcus-law.co.il
CDN_IMAGE_NAME: espocrm-extensions/outlook-addin-cdn
PROVIDER_URL: 'https://platform.dev.marcus-law.co.il/outlook-addin/OutlookAddin.vsto'
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Restore NuGet
run: nuget restore OutlookAddin.sln
- name: Build
run: msbuild OutlookAddin.sln /t:Build /p:Configuration=Release /p:Platform="Any CPU" /m
- name: Test
run: dotnet test src/OutlookAddin.Tests/OutlookAddin.Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=test-results.trx"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: '**/*.trx'
publish-clickonce:
needs: build
runs-on: windows-latest
if: startsWith(github.ref, 'refs/tags/v')
env:
MAGE: 'C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\mage.exe'
SIGNTOOL: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Compute version from tag
id: ver
shell: pwsh
run: |
$tag = "${{ github.ref_name }}"
if ($tag -notmatch '^v(\d+\.\d+\.\d+)$') {
throw "Tag $tag is not in vMAJOR.MINOR.PATCH form"
}
$version = "$($Matches[1]).0"
"version=$version" | Out-File -Append $env:GITHUB_OUTPUT
"versionDot=$($Matches[1])" | Out-File -Append $env:GITHUB_OUTPUT
"versionUscore=$($version -replace '\.','_')" | Out-File -Append $env:GITHUB_OUTPUT
Write-Host "Version: $version"
- name: Ensure code-signing cert in LocalMachine\My
shell: pwsh
env:
THUMB: ${{ secrets.CODESIGN_THUMBPRINT }}
PFX_B64: ${{ secrets.CODESIGN_CERT_PFX_BASE64 }}
PFX_PWD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
run: |
$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-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."
- name: Restore NuGet
run: nuget restore OutlookAddin.sln
- name: Build Release
run: msbuild OutlookAddin.sln /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: pwsh
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: pwsh
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: pwsh
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: Upload publish artifact
uses: actions/upload-artifact@v4
with:
name: clickonce-${{ steps.ver.outputs.versionDot }}
path: publish/
dockerize-and-deploy:
needs: publish-clickonce
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
env:
# Set this Gitea Actions secret AFTER the outlook-addin-cdn Coolify app
# is created. If empty, the trigger step is skipped (image still builds).
COOLIFY_UUID: ${{ secrets.COOLIFY_UUID }}
steps:
- name: Checkout (Dockerfile + nginx.conf)
uses: actions/checkout@v4
- name: Compute version from tag
id: ver
run: |
TAG="${GITHUB_REF##refs/tags/}"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Tag $TAG is not in vMAJOR.MINOR.PATCH form" >&2
exit 1
fi
VERSION="${TAG#v}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
- name: Download publish artifact
uses: actions/download-artifact@v4
with:
name: clickonce-${{ steps.ver.outputs.version }}
path: cdn/publish/
- name: Login to Gitea Registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ env.REGISTRY }} \
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
- name: Build and push image
run: |
BASE="${{ env.REGISTRY }}/${{ env.CDN_IMAGE_NAME }}"
docker build \
-t "${BASE}:latest" \
-t "${BASE}:${{ steps.ver.outputs.tag }}" \
-t "${BASE}:build-${{ github.run_number }}" \
cdn/
docker push "${BASE}:latest"
docker push "${BASE}:${{ steps.ver.outputs.tag }}"
docker push "${BASE}:build-${{ github.run_number }}"
- name: Trigger Coolify redeploy
if: env.COOLIFY_UUID != ''
run: |
curl -sf \
"http://coolify:8080/api/v1/deploy?uuid=${{ env.COOLIFY_UUID }}&force=true" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"
- name: Notify Mattermost (success)
if: success()
env:
WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK_RELEASES }}
run: |
if [ -z "$WEBHOOK" ]; then exit 0; fi
VERSION="${{ steps.ver.outputs.version }}"
curl -sf -X POST -H 'Content-Type: application/json' "$WEBHOOK" -d "$(cat <<EOF
{
"text": ":package: **OutlookAddin v$VERSION** שוחרר — https://platform.dev.marcus-law.co.il/outlook-addin/OutlookAddin.vsto",
"channel": "git-verelasim"
}
EOF
)"
- name: Notify Mattermost (failure)
if: failure()
env:
WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK_RELEASES }}
run: |
if [ -z "$WEBHOOK" ]; then exit 0; fi
curl -sf -X POST -H 'Content-Type: application/json' "$WEBHOOK" -d "$(cat <<EOF
{
"text": ":warning: OutlookAddin publish ל-${{ github.ref_name }} נכשל — ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
"channel": "git-verelasim"
}
EOF
)"