fix(task): declare create-only required fields #241
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Windows Installer | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'scripts/**' | |
| - 'packaging/**' | |
| - 'desktop/windows/**' | |
| - 'cmd/**' | |
| - 'internal/**' | |
| - 'go.mod' | |
| - 'go.sum' | |
| - '.github/workflows/windows-installer.yml' | |
| workflow_dispatch: | |
| inputs: | |
| test_tag: | |
| description: 'Optional temporary prerelease tag, for example v0.5.4-windows-test-20260802-1' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: write | |
| jobs: | |
| validate: | |
| name: Validate installer on Windows PowerShell 5.1 | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Test Windows private storage | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| go test ./internal/envstore ./internal/fs/securepath | |
| - name: Download and validate exact installer bytes | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $downloadedInstaller = Join-Path $env:RUNNER_TEMP 'install-downloaded.ps1' | |
| $installerUrl = 'https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/scripts/install/install.ps1' | |
| & .\scripts\test\test-install-windows-download.ps1 ` | |
| -Url $installerUrl ` | |
| -ExpectedInstallerPath .\scripts\install\install.ps1 ` | |
| -OutputPath $downloadedInstaller | |
| "DOWNLOADED_INSTALLER=$downloadedInstaller" | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append | |
| - name: Run full install and in-place upgrade as standard user | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $releaseRoot = Join-Path $env:RUNNER_TEMP 'agentdock-current-release' | |
| $distRoot = Join-Path $releaseRoot 'dist' | |
| $bundleRoot = Join-Path $distRoot 'share\agentdock\core-skills' | |
| New-Item -ItemType Directory -Path $distRoot -Force | Out-Null | |
| $env:CGO_ENABLED = '0' | |
| $env:GOOS = 'windows' | |
| $env:GOARCH = 'amd64' | |
| go build -trimpath -o (Join-Path $distRoot 'agentdock.exe') .\cmd\agentdock | |
| $controlPanelOutput = Join-Path $distRoot 'control-panel' | |
| dotnet publish .\desktop\windows\control-panel\AgentDock.ControlPanel.csproj ` | |
| -c Release ` | |
| -r win-x64 ` | |
| --self-contained true ` | |
| -o $controlPanelOutput | |
| Copy-Item (Join-Path $controlPanelOutput 'agentdock-tray.exe') (Join-Path $distRoot 'agentdock-tray.exe') -Force | |
| Copy-Item .\packaging\windows\assets\agentdock.ico (Join-Path $distRoot 'agentdock.ico') -Force | |
| Copy-Item .\scripts\install\manage-windows.ps1 (Join-Path $distRoot 'manage-windows.ps1') -Force | |
| python .\packaging\build-core-skill-bundle.py --output $bundleRoot | |
| $assetName = 'agentdock_windows_amd64.zip' | |
| $archivePath = Join-Path $releaseRoot $assetName | |
| Compress-Archive ` | |
| -Path (Join-Path $distRoot 'agentdock.exe'), (Join-Path $distRoot 'agentdock-tray.exe'), (Join-Path $distRoot 'agentdock.ico'), (Join-Path $distRoot 'manage-windows.ps1'), (Join-Path $distRoot 'share') ` | |
| -DestinationPath $archivePath ` | |
| -Force | |
| $hash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| [IO.File]::WriteAllText( | |
| "$archivePath.sha256", | |
| "$hash $assetName`n", | |
| [Text.UTF8Encoding]::new($false) | |
| ) | |
| $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0) | |
| $listener.Start() | |
| $port = ([Net.IPEndPoint] $listener.LocalEndpoint).Port | |
| $listener.Stop() | |
| $releaseBaseUrl = "http://127.0.0.1:$port" | |
| $server = Start-Process ` | |
| -FilePath 'python' ` | |
| -ArgumentList @('-m', 'http.server', $port, '--bind', '127.0.0.1', '--directory', $releaseRoot) ` | |
| -WindowStyle Hidden ` | |
| -PassThru | |
| try { | |
| $deadline = [DateTime]::UtcNow.AddSeconds(15) | |
| do { | |
| Start-Sleep -Milliseconds 250 | |
| try { | |
| Invoke-WebRequest -UseBasicParsing -Uri "$releaseBaseUrl/$assetName.sha256" -TimeoutSec 2 | Out-Null | |
| break | |
| } catch { | |
| if ([DateTime]::UtcNow -ge $deadline) { throw } | |
| } | |
| } while ($true) | |
| & .\scripts\test\run-windows-installer-e2e-as-standard-user.ps1 ` | |
| -InstallerPath $env:DOWNLOADED_INSTALLER ` | |
| -Version latest ` | |
| -ReleaseBaseUrl $releaseBaseUrl | |
| } finally { | |
| Stop-Process -Id $server.Id -Force -ErrorAction SilentlyContinue | |
| } | |
| - name: Build current Windows binary | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| New-Item -ItemType Directory -Path dist -Force | Out-Null | |
| $env:CGO_ENABLED = '0' | |
| $env:GOOS = 'windows' | |
| $env:GOARCH = 'amd64' | |
| go build -trimpath -o dist\agentdock.exe .\cmd\agentdock | |
| dotnet publish .\desktop\windows\control-panel\AgentDock.ControlPanel.csproj ` | |
| -c Release ` | |
| -r win-x64 ` | |
| --self-contained true ` | |
| -o dist\control-panel | |
| Copy-Item .\dist\control-panel\agentdock-tray.exe .\dist\agentdock-tray.exe -Force | |
| Copy-Item .\scripts\install\manage-windows.ps1 .\dist\manage-windows.ps1 -Force | |
| - name: Test configured Authenticode certificate | |
| shell: pwsh | |
| env: | |
| WINDOWS_SIGNING_CERT_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_BASE64 }} | |
| WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| if ([string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_BASE64) -or | |
| [string]::IsNullOrWhiteSpace($env:WINDOWS_SIGNING_CERT_PASSWORD)) { | |
| if (-not [string]::IsNullOrWhiteSpace('${{ inputs.test_tag }}')) { | |
| throw 'Windows signing secrets are required for a test package.' | |
| } | |
| Write-Host 'Windows signing secrets are not configured; skipping Authenticode integration test.' | |
| exit 0 | |
| } | |
| $paths = @('.\dist\agentdock.exe', '.\dist\agentdock-tray.exe') | |
| .\packaging\windows\sign-windows.ps1 -Path $paths | |
| .\packaging\windows\sign-windows.ps1 -Path $paths -VerifyOnly | |
| - name: Prepare offline AMD64 payload | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $payloadRoot = Join-Path $env:RUNNER_TEMP 'agentdock-offline-amd64' | |
| Remove-Item -LiteralPath $payloadRoot -Recurse -Force -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $payloadRoot -Force | Out-Null | |
| python .\packaging\build-core-skill-bundle.py --output .\dist\share\agentdock\core-skills | |
| Copy-Item .\packaging\windows\assets\agentdock.ico .\dist\agentdock.ico -Force | |
| $archivePath = Join-Path $payloadRoot 'agentdock_windows_amd64.zip' | |
| Compress-Archive ` | |
| -Path .\dist\agentdock.exe, .\dist\agentdock-tray.exe, .\dist\agentdock.ico, .\dist\manage-windows.ps1, .\dist\share ` | |
| -DestinationPath $archivePath ` | |
| -Force | |
| $hash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| $checksumPath = "$archivePath.sha256" | |
| [IO.File]::WriteAllText($checksumPath, "$hash agentdock_windows_amd64.zip`n", [Text.UTF8Encoding]::new($false)) | |
| $cloudflaredPath = Join-Path $payloadRoot 'cloudflared-windows-amd64.exe' | |
| Invoke-WebRequest ` | |
| -UseBasicParsing ` | |
| -Uri 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe' ` | |
| -OutFile $cloudflaredPath | |
| $cloudflaredSignature = Get-AuthenticodeSignature -LiteralPath $cloudflaredPath | |
| if ($cloudflaredSignature.Status -ne [Management.Automation.SignatureStatus]::Valid) { | |
| throw "cloudflared Authenticode signature is invalid: $($cloudflaredSignature.StatusMessage)" | |
| } | |
| "AMD64_ARCHIVE=$archivePath" | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append | |
| "AMD64_CHECKSUM=$checksumPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append | |
| "AMD64_CLOUDFLARED=$cloudflaredPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding ascii -Append | |
| - name: Test Quick Tunnel restart lifecycle | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $fakeCloudflared = Join-Path $env:RUNNER_TEMP 'fake-cloudflared.exe' | |
| $env:CGO_ENABLED = '0' | |
| $env:GOOS = 'windows' | |
| $env:GOARCH = 'amd64' | |
| go build -trimpath -o $fakeCloudflared .\scripts\test\testdata\fake-cloudflared | |
| & .\scripts\test\test-windows-quick-tunnel-lifecycle.ps1 ` | |
| -InstallerPath .\scripts\install\install.ps1 ` | |
| -UninstallerPath .\scripts\install\uninstall-windows.ps1 ` | |
| -AgentDockArchive $env:AMD64_ARCHIVE ` | |
| -AgentDockChecksumFile $env:AMD64_CHECKSUM ` | |
| -FakeCloudflaredBinary $fakeCloudflared | |
| - name: Compile offline AMD64 Setup | |
| shell: pwsh | |
| env: | |
| WINDOWS_SIGNING_CERT_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_BASE64 }} | |
| WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $testTag = '${{ inputs.test_tag }}' | |
| if ([string]::IsNullOrWhiteSpace($testTag)) { | |
| $buildInfo = Get-Content -LiteralPath '.\internal\buildinfo\buildinfo.go' -Raw | |
| $match = [regex]::Match($buildInfo, 'const\s+Version\s*=\s*"([^"]+)"') | |
| if (-not $match.Success) { throw 'Could not read buildinfo.Version.' } | |
| $version = $match.Groups[1].Value | |
| } else { | |
| if (-not $testTag.StartsWith('v')) { throw 'The test tag must start with v.' } | |
| $version = $testTag.Substring(1) | |
| } | |
| $outputDir = Join-Path $env:RUNNER_TEMP 'agentdock-setup' | |
| $parameters = @{ | |
| Version = $version | |
| Architecture = 'amd64' | |
| AgentDockArchive = $env:AMD64_ARCHIVE | |
| AgentDockChecksumFile = $env:AMD64_CHECKSUM | |
| CloudflaredBinary = $env:AMD64_CLOUDFLARED | |
| OutputDirectory = $outputDir | |
| } | |
| if (-not [string]::IsNullOrWhiteSpace($testTag)) { | |
| $parameters.SignedBuild = $true | |
| } | |
| & .\packaging\windows\build-windows-offline-setup.ps1 @parameters | |
| - name: Run Setup install and uninstall E2E | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| & .\scripts\test\test-windows-setup-e2e.ps1 ` | |
| -SetupPath (Join-Path $env:RUNNER_TEMP 'agentdock-setup\AgentDockSetup-amd64.exe') ` | |
| -AllowLegacyTaskMutation | |
| - name: Smoke test current binary as standard user | |
| shell: powershell | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| & .\scripts\test\test-windows-current-binary-as-standard-user.ps1 ` | |
| -BinaryPath .\dist\agentdock.exe | |
| - name: Build and publish signed Windows test package | |
| if: ${{ inputs.test_tag != '' }} | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| WINDOWS_SIGNING_CERT_BASE64: ${{ secrets.WINDOWS_SIGNING_CERT_BASE64 }} | |
| WINDOWS_SIGNING_CERT_PASSWORD: ${{ secrets.WINDOWS_SIGNING_CERT_PASSWORD }} | |
| TEST_TAG: ${{ inputs.test_tag }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $releaseDir = Join-Path $env:RUNNER_TEMP 'agentdock-windows-test-release' | |
| Remove-Item -LiteralPath $releaseDir -Recurse -Force -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Path $releaseDir -Force | Out-Null | |
| Copy-Item -LiteralPath $env:AMD64_ARCHIVE -Destination $releaseDir -Force | |
| Copy-Item -LiteralPath $env:AMD64_CHECKSUM -Destination $releaseDir -Force | |
| function New-WindowsAsset { | |
| param( | |
| [Parameter(Mandatory = $true)][string] $Architecture, | |
| [Parameter(Mandatory = $true)][string] $SourceDirectory | |
| ) | |
| $assetName = "agentdock_windows_$Architecture.zip" | |
| $assetPath = Join-Path $releaseDir $assetName | |
| Compress-Archive ` | |
| -Path (Join-Path $SourceDirectory 'agentdock.exe'), (Join-Path $SourceDirectory 'agentdock-tray.exe'), (Join-Path $SourceDirectory 'agentdock.ico'), (Join-Path $SourceDirectory 'manage-windows.ps1'), (Join-Path $SourceDirectory 'share') ` | |
| -DestinationPath $assetPath ` | |
| -Force | |
| $hash = (Get-FileHash -LiteralPath $assetPath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| [IO.File]::WriteAllText("$assetPath.sha256", "$hash $assetName`n", [Text.UTF8Encoding]::new($false)) | |
| } | |
| $arm64Dir = Join-Path $env:RUNNER_TEMP 'agentdock-windows-arm64' | |
| New-Item -ItemType Directory -Path $arm64Dir -Force | Out-Null | |
| $env:CGO_ENABLED = '0' | |
| $env:GOOS = 'windows' | |
| $env:GOARCH = 'arm64' | |
| go build -trimpath -o (Join-Path $arm64Dir 'agentdock.exe') .\cmd\agentdock | |
| $arm64ControlPanelOutput = Join-Path $arm64Dir 'control-panel' | |
| dotnet publish .\desktop\windows\control-panel\AgentDock.ControlPanel.csproj ` | |
| -c Release ` | |
| -r win-arm64 ` | |
| --self-contained true ` | |
| -o $arm64ControlPanelOutput | |
| Copy-Item (Join-Path $arm64ControlPanelOutput 'agentdock-tray.exe') (Join-Path $arm64Dir 'agentdock-tray.exe') -Force | |
| Copy-Item .\scripts\install\manage-windows.ps1 (Join-Path $arm64Dir 'manage-windows.ps1') -Force | |
| .\packaging\windows\sign-windows.ps1 -Path @( | |
| (Join-Path $arm64Dir 'agentdock.exe'), | |
| (Join-Path $arm64Dir 'agentdock-tray.exe') | |
| ) | |
| python .\packaging\build-core-skill-bundle.py --output (Join-Path $arm64Dir 'share\agentdock\core-skills') | |
| Copy-Item .\packaging\windows\assets\agentdock.ico (Join-Path $arm64Dir 'agentdock.ico') -Force | |
| New-WindowsAsset -Architecture 'arm64' -SourceDirectory $arm64Dir | |
| $arm64Archive = Join-Path $releaseDir 'agentdock_windows_arm64.zip' | |
| & .\packaging\windows\build-windows-offline-setup.ps1 ` | |
| -Version ($env:TEST_TAG.TrimStart('v')) ` | |
| -Architecture arm64 ` | |
| -AgentDockArchive $arm64Archive ` | |
| -AgentDockChecksumFile "$arm64Archive.sha256" ` | |
| -CloudflaredBinary $env:AMD64_CLOUDFLARED ` | |
| -OutputDirectory $releaseDir ` | |
| -SignedBuild | |
| $amd64Setup = Join-Path $env:RUNNER_TEMP 'agentdock-setup\AgentDockSetup-amd64.exe' | |
| Copy-Item -LiteralPath $amd64Setup -Destination $releaseDir -Force | |
| foreach ($setupName in @('AgentDockSetup-amd64.exe', 'AgentDockSetup-arm64.exe')) { | |
| $setupPath = Join-Path $releaseDir $setupName | |
| $setupHash = (Get-FileHash -LiteralPath $setupPath -Algorithm SHA256).Hash.ToLowerInvariant() | |
| [IO.File]::WriteAllText( | |
| "$setupPath.sha256", | |
| "$setupHash $setupName`n", | |
| [Text.UTF8Encoding]::new($false) | |
| ) | |
| } | |
| gh release delete $env:TEST_TAG --repo $env:GITHUB_REPOSITORY --cleanup-tag --yes 2>$null | |
| gh release create $env:TEST_TAG ` | |
| (Get-ChildItem -LiteralPath $releaseDir -File | ForEach-Object FullName) ` | |
| --repo $env:GITHUB_REPOSITORY ` | |
| --target $env:GITHUB_SHA ` | |
| --prerelease ` | |
| --title "AgentDock Windows test $env:TEST_TAG" ` | |
| --notes "Temporary Windows test build from commit $env:GITHUB_SHA. Uses the existing AgentDock self-signed Authenticode certificate." |