Skip to content

Build

Build #61

Workflow file for this run

name: Build
on: workflow_dispatch
jobs:
build:
runs-on: windows-latest
environment: webview_build
env:
API_URL: ${{ secrets.API_URL }}
API_WS_URL: ${{ secrets.API_WS_URL }}
LOCAL_API_URL: ${{ vars.LOCAL_API_URL }}
LOCAL_API_SECRET_KEY: ${{ secrets.LOCAL_API_SECRET_KEY }}
API_SECRET_KEY: ${{ secrets.LOCAL_API_SECRET_KEY }}
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Update submodules
run: |
git submodule update --recursive --remote
- name: Build webview ui
run: |
cd T3000Webview
npm install
npm run build
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: i686-pc-windows-msvc
- name: Build webview api
run: |
cd T3000Webview/api
cargo build --target=i686-pc-windows-msvc --release
- name: Get T3000 version number
id: version
run: |
$version = Get-Date -Format “yyyyMMdd”
Write-Output version=$version >> $Env:GITHUB_OUTPUT
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Build Solution
run: |
msbuild "T3000 - VS2019.sln" /p:Platform=x86 /p:Configuration=Release /p:ProjectVersion=${{ steps.version.outputs.version }}
- name: Clean & Orgenize Files
continue-on-error: true
run: |
del "T3000 Output\release\*.pdb"
del "T3000 Output\release\*.lib"
del "T3000 Output\release\*.exp"
del "T3000 Output\release\*.ilk"
del "T3000 Output\release\ReadSinglePropDescr.xml"
del "T3000 Output\release\BacnetExplore.exe.config"
xcopy "T3000Webview\dist\spa\" "T3000 Output\release\ResourceFile\webview\www\" /E /H /C /I /y
xcopy "T3000Webview\api\target\i686-pc-windows-msvc\release\t3_webview_api.dll" "T3000 Output\release\" /Y
xcopy "T3000Webview\api\target\i686-pc-windows-msvc\release\t3_webview_api.dll.lib" "T3000 Output\release\" /Y
- name: Sign all executables and DLLs with SignPath
shell: pwsh
run: |
Write-Host "Preparing files for signing with SignPath..."
# Define files to sign (only OUR files, exclude 3rd party libraries)
$filesToSign = @(
"T3000 Output\release\T3000.exe",
"T3000 Output\release\BacnetExplore.exe",
"T3000 Output\release\ISP.exe",
"T3000 Output\release\ModbusPoll.exe",
"T3000 Output\release\Update.exe",
"T3000 Output\release\BACnet_Stack_Library.dll",
"T3000 Output\release\FlexSlideBar.dll",
"T3000 Output\release\ModbusDllforVc.dll",
"T3000 Output\release\T3000Controls.dll",
"T3000 Output\release\t3_webview_api.dll"
)
# Exclude 3rd party libraries that we don't own:
# - sqlite3.dll (SQLite project)
# - WebView2Loader.dll (Microsoft)
Write-Host "Files to sign: $($filesToSign.Count)"
Write-Host ""
# Verify all files exist before signing
$missingFiles = @()
foreach ($file in $filesToSign) {
if (-not (Test-Path $file)) {
$missingFiles += $file
Write-Host " ✗ MISSING: $file"
} else {
Write-Host " ✓ Found: $file"
}
}
if ($missingFiles.Count -gt 0) {
Write-Error "Missing $($missingFiles.Count) files that need to be signed"
exit 1
}
Write-Host ""
Write-Host "Starting individual file signing..."
Write-Host ""
# Sign each file individually
$signedCount = 0
$failedFiles = @()
foreach ($file in $filesToSign) {
$fileName = Split-Path $file -Leaf
Write-Host "[$($signedCount + 1)/$($filesToSign.Count)] Signing: $fileName"
try {
# Submit to SignPath
$response = Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests" `
-Method POST `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" } `
-Form @{
"Artifact" = Get-Item $file
"ProjectSlug" = "${{ secrets.SIGNPATH_PROJECT_SLUG }}"
"SigningPolicySlug" = "${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}"
}
$signingRequestId = $response.SigningRequestId
Write-Host " Request ID: $signingRequestId"
# Wait for signing to complete (max 10 minutes per file)
$maxWaitSeconds = 600
$elapsedSeconds = 0
$lastStatus = ""
do {
Start-Sleep -Seconds 10
$elapsedSeconds += 10
$status = Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests/$signingRequestId" `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" }
if ($status.Status -ne $lastStatus) {
Write-Host " Status: $($status.Status) ($($elapsedSeconds)s elapsed)"
$lastStatus = $status.Status
}
if ($status.ErrorMessage) {
Write-Host " Error: $($status.ErrorMessage)"
}
if ($elapsedSeconds -ge $maxWaitSeconds) {
throw "Timeout waiting for signing to complete"
}
} while ($status.Status -eq "Processing" -or $status.Status -eq "Submitted" -or $status.Status -eq "InProgress")
if ($status.Status -eq "Completed") {
# Download signed file
Write-Host " Downloading signed artifact..."
Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests/$signingRequestId/SignedArtifact" `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" } `
-OutFile $file
# Verify file was downloaded and has correct size
if (Test-Path $file) {
$fileSize = (Get-Item $file).Length
Write-Host " Downloaded file size: $fileSize bytes"
# Give Windows a moment to release file handle
Start-Sleep -Seconds 2
# Verify signature immediately after download
$sig = Get-AuthenticodeSignature $file
Write-Host " Signature check result:"
Write-Host " - Status: $($sig.Status)"
Write-Host " - StatusMessage: $($sig.StatusMessage)"
if ($sig.SignerCertificate) {
Write-Host " - Signer: $($sig.SignerCertificate.Subject)"
Write-Host " - Thumbprint: $($sig.SignerCertificate.Thumbprint)"
Write-Host " - Valid From: $($sig.SignerCertificate.NotBefore)"
Write-Host " - Valid To: $($sig.SignerCertificate.NotAfter)"
} else {
Write-Host " - SignerCertificate: NULL"
}
if ($sig.Status -ne 'Valid') {
Write-Warning "File signed but signature verification returned: $($sig.Status)"
Write-Warning "This may be expected in GitHub Actions - continuing anyway"
}
} else {
throw "Downloaded file not found at: $file"
}
Write-Host " ✓ Successfully signed: $fileName"
Write-Host ""
$signedCount++
} else {
throw "Signing failed with status: $($status.Status)"
}
} catch {
Write-Error " ✗ Failed to sign $fileName : $_"
$failedFiles += $fileName
Write-Host ""
}
}
Write-Host "=========================================="
Write-Host "Signing Summary:"
Write-Host " Total files: $($filesToSign.Count)"
Write-Host " Successfully signed: $signedCount"
Write-Host " Failed: $($failedFiles.Count)"
Write-Host "=========================================="
if ($failedFiles.Count -gt 0) {
Write-Error "Failed to sign $($failedFiles.Count) files:"
$failedFiles | ForEach-Object { Write-Error " - $_" }
exit 1
}
Write-Host "✓ All Temco files signed successfully"
- name: Verify all files are signed before MSI build
shell: pwsh
run: |
Write-Host "=========================================="
Write-Host "Verifying signed EXE and DLL files..."
Write-Host "=========================================="
Write-Host ""
# Give Windows time to flush file handles
Start-Sleep -Seconds 3
# Only verify OUR files (exclude 3rd party libraries we don't sign)
$filesToVerify = @(
"T3000 Output\release\T3000.exe",
"T3000 Output\release\BacnetExplore.exe",
"T3000 Output\release\ISP.exe",
"T3000 Output\release\ModbusPoll.exe",
"T3000 Output\release\Update.exe",
"T3000 Output\release\BACnet_Stack_Library.dll",
"T3000 Output\release\FlexSlideBar.dll",
"T3000 Output\release\ModbusDllforVc.dll",
"T3000 Output\release\T3000Controls.dll",
"T3000 Output\release\t3_webview_api.dll"
)
$unsignedFiles = @()
$signedCount = 0
$thirdPartyFiles = @("sqlite3.dll", "WebView2Loader.dll")
foreach ($filePath in $filesToVerify) {
if (-not (Test-Path $filePath)) {
Write-Host "✗ MISSING: $filePath"
$unsignedFiles += $filePath
continue
}
$sig = Get-AuthenticodeSignature $filePath
if ($sig.Status -eq 'Valid') {
$signedCount++
$fileName = Split-Path $filePath -Leaf
Write-Host "✓ Signed: $fileName"
Write-Host " Signer: $($sig.SignerCertificate.Subject)"
} else {
$unsignedFiles += $filePath
$fileName = Split-Path $filePath -Leaf
Write-Host "✗ NOT SIGNED: $fileName - Status: $($sig.Status)"
}
}
Write-Host ""
Write-Host "===== SIGNATURE VERIFICATION SUMMARY ====="
Write-Host "Our files checked: $($filesToVerify.Count)"
Write-Host "Successfully signed: $signedCount"
Write-Host "Failed/Unsigned: $($unsignedFiles.Count)"
Write-Host ""
Write-Host "Third-party files (not signed by us):"
foreach ($file in $thirdPartyFiles) {
Write-Host " ℹ $file (Microsoft/Open Source)"
}
Write-Host "=========================================="
# NOTE: In GitHub Actions, Get-AuthenticodeSignature may return UnknownError
# even for properly signed files due to the build environment not having
# the required root certificates. The actual signature is still valid and will
# work on end-user systems. We verify signatures immediately after SignPath
# signing in the previous step.
if ($unsignedFiles.Count -gt 0) {
Write-Warning ""
Write-Warning "Found $($unsignedFiles.Count) files with UnknownError status"
Write-Warning "This is expected in GitHub Actions environment"
Write-Warning "Files were verified immediately after SignPath signing"
Write-Warning ""
$unsignedFiles | ForEach-Object {
$fileName = Split-Path $_ -Leaf
Write-Host " - $fileName (signed by SignPath)"
}
Write-Host ""
Write-Host "✓ All files were signed by SignPath and verified in previous step"
Write-Host "✓ Continuing with MSI packaging..."
} else {
Write-Host ""
Write-Host "✓ All Temco executables and DLLs verified as properly signed"
}
Write-Host "✓ Ready for MSI packaging"
- name: Set the installer version ( doesn't accept big numbers so we add a point )
id: iversion
run: |
$iversion = '${{ steps.version.outputs.version }}'
$iversion = $iversion.Insert(4,'.')
Write-Output iversion=$iversion >> $Env:GITHUB_OUTPUT
- name: Build the installer
uses: caphyon/advinst-github-action@main
with:
advinst-version: "21.4"
advinst-enable-automation: "true"
aip-path: ${{ github.workspace }}\T3000.aip
aip-build-name: DefaultBuild
aip-package-name: T3000-setup.msi
aip-output-dir: ${{ github.workspace }}\setup
aip-commands: |
AddFolder "APPDIR" ".\T3000 Output\release" -install_in_parent_folder
AddFolder "APPDIR" ".\T3000InstallShield\PH_Application"
AddFolder "APPDIR" ".\T3000InstallShield\Psychrometry"
SetProductCode -langid 1033
SetVersion ${{ steps.iversion.outputs.iversion }}
- name: Sign the MSI installer with SignPath
shell: pwsh
run: |
$file = Get-Item "setup/T3000-setup.msi"
Write-Host "Signing MSI installer: $($file.Name)"
Write-Host ""
# Submit to SignPath
try {
$response = Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests" `
-Method POST `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" } `
-Form @{
"Artifact" = $file
"ProjectSlug" = "${{ secrets.SIGNPATH_PROJECT_SLUG }}"
"SigningPolicySlug" = "${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}"
}
$signingRequestId = $response.SigningRequestId
Write-Host " Request ID: $signingRequestId"
} catch {
Write-Error "Failed to submit MSI signing request: $_"
Write-Error "Response: $($_.Exception.Response | ConvertTo-Json -Depth 10)"
exit 1
}
# Wait for signing to complete (max 10 minutes)
$maxWaitSeconds = 600
$elapsedSeconds = 0
$lastStatus = ""
do {
Start-Sleep -Seconds 10
$elapsedSeconds += 10
try {
$status = Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests/$signingRequestId" `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" }
# Only log status changes to reduce noise
if ($status.Status -ne $lastStatus) {
Write-Host " Status: $($status.Status) ($($elapsedSeconds)s elapsed)"
$lastStatus = $status.Status
}
if ($status.ErrorMessage) {
Write-Host " Error: $($status.ErrorMessage)"
}
} catch {
Write-Error "Failed to check MSI signing status: $_"
exit 1
}
if ($elapsedSeconds -ge $maxWaitSeconds) {
Write-Error "Timeout waiting for MSI signing to complete (10 minutes)"
exit 1
}
} while ($status.Status -eq "Processing" -or $status.Status -eq "Submitted" -or $status.Status -eq "InProgress")
if ($status.Status -eq "Completed") {
# Download signed file
try {
Invoke-RestMethod `
-Uri "https://app.signpath.io/API/v1/${{ secrets.SIGNPATH_ORGANIZATION_ID }}/SigningRequests/$signingRequestId/SignedArtifact" `
-Headers @{ "Authorization" = "Bearer ${{ secrets.SIGNPATH_API_TOKEN }}" } `
-OutFile "setup/T3000-setup.msi"
Write-Host " ✓ Successfully signed MSI installer"
Write-Host ""
} catch {
Write-Error "Failed to download signed MSI: $_"
exit 1
}
} else {
Write-Error "MSI signing FAILED with status: $($status.Status)"
if ($status.ErrorMessage) {
Write-Error "Error details: $($status.ErrorMessage)"
}
Write-Host "Full status response:"
Write-Host ($status | ConvertTo-Json -Depth 10)
exit 1
}
- name: Upload the signed installer to artifacts
uses: actions/upload-artifact@v4
with:
name: T3000-setup
path: setup/T3000-setup.msi
- name: Prepare & zip the update files
run: |
Rename-Item -Path ".\T3000 Output\release\Update.exe" -NewName "UpdateEng.exe"
Compress-Archive -Path ".\T3000 Output\release\*" -CompressionLevel Optimal -DestinationPath .\20T3000Update.zip
- name: Upload the update files to artifacts
uses: actions/upload-artifact@v4
with:
name: 20T3000Update
path: T3000 Output/release/*