chore: release v0.47.7 #138
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 Install Test | |
| on: | |
| pull_request: | |
| paths: | |
| - 'installer/**' | |
| - 'rel/overlays/**' | |
| - 'tray/**' | |
| - 'config/runtime.exs' | |
| - 'mix.exs' | |
| - '.github/workflows/windows-install-test.yml' | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'installer/**' | |
| - 'rel/overlays/**' | |
| - 'tray/**' | |
| - 'config/runtime.exs' | |
| - 'mix.exs' | |
| - '.github/workflows/windows-install-test.yml' | |
| workflow_dispatch: | |
| jobs: | |
| build-windows: | |
| name: Build Windows artifacts | |
| runs-on: windows-latest | |
| env: | |
| MIX_ENV: prod | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set synthetic CI version | |
| shell: python | |
| run: | | |
| import re | |
| with open('mix.exs') as f: | |
| content = f.read() | |
| updated = re.sub(r'version: "[^"]+"', 'version: "0.0.0"', content) | |
| with open('mix.exs', 'w') as f: | |
| f.write(updated) | |
| - uses: erlef/setup-beam@v1 | |
| with: | |
| elixir-version: '1.19' | |
| otp-version: '28' | |
| - name: Cache Mix deps and build | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| deps | |
| _build | |
| key: windows-mix-prod-1.19-28-${{ hashFiles('**/mix.lock') }} | |
| restore-keys: windows-mix-prod-1.19-28- | |
| - name: Install dependencies | |
| run: mix deps.get --only prod | |
| - name: Compile | |
| run: mix compile | |
| env: | |
| MIX_OS_DEPS_COMPILE_PARTITION_COUNT: "8" | |
| - name: Deploy assets | |
| run: mix assets.deploy | |
| - name: Build release | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "_build/prod/rel") { Remove-Item -Recurse -Force "_build/prod/rel" } | |
| mix release | |
| env: | |
| MIX_OS_DEPS_COMPILE_PARTITION_COUNT: "8" | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.22' | |
| cache-dependency-path: tray/go.sum | |
| - name: Build tray binary | |
| shell: pwsh | |
| run: | | |
| cd tray | |
| go build -ldflags="-H windowsgui" -o ../scry2-tray.exe . | |
| - name: Package zip | |
| shell: pwsh | |
| run: | | |
| $ARCHIVE = "scry_2-v0.0.0-windows-x86_64" | |
| New-Item -ItemType Directory -Name $ARCHIVE | |
| Copy-Item -Recurse "_build\prod\rel\scry_2\*" "$ARCHIVE\" | |
| Copy-Item "scry2-tray.exe" "$ARCHIVE\scry2-tray.exe" | |
| Compress-Archive -Path "$ARCHIVE" -DestinationPath "${ARCHIVE}.zip" | |
| - name: Install .NET SDK | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '8.0.x' | |
| - name: Install WiX v5 | |
| shell: pwsh | |
| run: | | |
| dotnet tool install --global wix --version 5.0.2 | |
| wix extension add WixToolset.UI.wixext/5.0.2 | |
| wix extension add WixToolset.Util.wixext/5.0.2 | |
| wix extension add WixToolset.Firewall.wixext/5.0.2 | |
| - name: Build MSI installer | |
| shell: pwsh | |
| run: | | |
| installer/scripts/build-msi.ps1 ` | |
| -Version "0.0.0" ` | |
| -TrayExe scry2-tray.exe | |
| - name: Upload zip artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: scry2-zip | |
| path: scry_2-v0.0.0-windows-x86_64.zip | |
| - name: Upload MSI artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: scry2-msi | |
| path: installer/output/Scry2-0.0.0.msi | |
| test-zip-install: | |
| name: Test zip installation | |
| runs-on: windows-latest | |
| needs: build-windows | |
| env: | |
| SCRY2_QUIET: "1" | |
| steps: | |
| - name: Download zip artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: scry2-zip | |
| - name: Extract zip | |
| shell: pwsh | |
| run: Expand-Archive -Path "scry_2-v0.0.0-windows-x86_64.zip" -DestinationPath . | |
| - name: Run install.bat | |
| shell: cmd | |
| run: scry_2-v0.0.0-windows-x86_64\install.bat | |
| - name: Verify file layout | |
| shell: pwsh | |
| run: | | |
| $installDir = "$env:LOCALAPPDATA\scry_2" | |
| if (-not (Test-Path "$installDir\scry2-tray.exe")) { throw "scry2-tray.exe missing from $installDir" } | |
| if (-not (Test-Path "$installDir\bin\scry_2.bat")) { throw "bin\scry_2.bat missing from $installDir" } | |
| Write-Host "File layout OK: tray and release bin present" | |
| - name: Verify registry autostart | |
| shell: pwsh | |
| run: | | |
| $val = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Scry2" -ErrorAction Stop | |
| if ($val.Scry2 -notlike "*scry2-tray.exe*") { throw "Autostart registry value incorrect: $($val.Scry2)" } | |
| Write-Host "Registry autostart OK: $($val.Scry2)" | |
| - name: Start backend and verify web UI | |
| shell: pwsh | |
| run: | | |
| $installDir = "$env:LOCALAPPDATA\scry_2" | |
| Start-Process -FilePath "$installDir\bin\scry_2.bat" -ArgumentList "start" -WindowStyle Hidden | |
| $timeout = 60 | |
| $start = Get-Date | |
| $success = $false | |
| while ((Get-Date) -lt $start.AddSeconds($timeout)) { | |
| try { | |
| $response = Invoke-WebRequest -Uri "http://localhost:6015" -UseBasicParsing -TimeoutSec 3 | |
| if ($response.StatusCode -eq 200) { | |
| Write-Host "Web UI responded with HTTP 200" | |
| $success = $true | |
| break | |
| } | |
| } catch { } | |
| Start-Sleep -Seconds 2 | |
| } | |
| if (-not $success) { throw "Web UI did not respond within ${timeout}s" } | |
| - name: Stop backend | |
| shell: pwsh | |
| run: | | |
| Start-Process -FilePath "$env:LOCALAPPDATA\scry_2\bin\scry_2.bat" -ArgumentList "stop" -Wait -WindowStyle Hidden | |
| - name: Kill remaining processes and uninstall | |
| shell: pwsh | |
| run: | | |
| $installDir = "$env:LOCALAPPDATA\scry_2" | |
| # Kill any lingering erlang/epmd processes under the install dir | |
| Get-Process | Where-Object { $_.Path -and $_.Path.StartsWith($installDir, [System.StringComparison]::OrdinalIgnoreCase) } | Stop-Process -Force -ErrorAction SilentlyContinue | |
| Start-Sleep -Seconds 2 | |
| # Remove registry autostart | |
| Remove-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Scry2" -ErrorAction SilentlyContinue | |
| # Remove install directory | |
| if (Test-Path $installDir) { Remove-Item -Recurse -Force $installDir -ErrorAction SilentlyContinue } | |
| Start-Sleep -Seconds 2 | |
| # Retry if locked | |
| if (Test-Path $installDir) { Remove-Item -Recurse -Force $installDir } | |
| - name: Verify cleanup | |
| shell: pwsh | |
| run: | | |
| $timeout = 30 | |
| $start = Get-Date | |
| while ((Get-Date) -lt $start.AddSeconds($timeout)) { | |
| if (-not (Test-Path "$env:LOCALAPPDATA\scry_2")) { | |
| Write-Host "Install directory removed" | |
| return | |
| } | |
| Start-Sleep -Seconds 2 | |
| } | |
| throw "Install directory still present after ${timeout}s" | |
| - name: Verify cleanup | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "$env:LOCALAPPDATA\scry_2") { throw "Install directory not removed" } | |
| $autostart = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Scry2" -ErrorAction SilentlyContinue | |
| if ($autostart) { throw "Autostart registry key not removed" } | |
| Write-Host "Cleanup OK: install dir gone, registry key removed" | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: scry2-logs-zip | |
| path: | | |
| ${{ env.APPDATA }}\scry_2\log\ | |
| if-no-files-found: ignore | |
| test-msi-install: | |
| name: Test MSI installation | |
| runs-on: windows-latest | |
| needs: build-windows | |
| steps: | |
| - name: Download MSI artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: scry2-msi | |
| - name: Validate MSI before install | |
| shell: pwsh | |
| run: | | |
| $msi = Get-ChildItem -Filter "Scry2-*.msi" | Select-Object -First 1 | |
| Write-Host "MSI file: $($msi.Name), Size: $($msi.Length) bytes" | |
| # Query MSI tables using COM | |
| $installer = New-Object -ComObject WindowsInstaller.Installer | |
| $db = $installer.OpenDatabase($msi.FullName, 0) | |
| $view = $db.OpenView("SELECT Name FROM _Tables") | |
| $view.Execute() | |
| $tables = @() | |
| while ($record = $view.Fetch()) { $tables += $record.StringData(1) } | |
| Write-Host "MSI tables ($($tables.Count)): $($tables -join ', ')" | |
| if ($tables -notcontains "Component") { Write-Host "WARNING: Component table missing!" } | |
| if ($tables -notcontains "Feature") { Write-Host "WARNING: Feature table missing!" } | |
| # Count components | |
| try { | |
| $view2 = $db.OpenView("SELECT COUNT(*) FROM Component") | |
| $view2.Execute() | |
| $r = $view2.Fetch() | |
| Write-Host "Component count: $($r.IntegerData(1))" | |
| } catch { Write-Host "Could not query Component table: $_" } | |
| # Dump CustomAction table to verify DeleteInstallDir is present | |
| Write-Host "" | |
| Write-Host "=== CustomAction table ===" | |
| try { | |
| $view3 = $db.OpenView("SELECT Action, Type, Source, Target FROM CustomAction") | |
| $view3.Execute() | |
| while ($r = $view3.Fetch()) { | |
| $action = $r.StringData(1) | |
| $type = $r.IntegerData(2) | |
| $source = $r.StringData(3) | |
| $target = $r.StringData(4) | |
| Write-Host " $action | type=$type | source=$source | target=$target" | |
| } | |
| } catch { Write-Host "Could not query CustomAction table: $_" } | |
| # Dump InstallExecuteSequence entries around RemoveFiles | |
| Write-Host "" | |
| Write-Host "=== InstallExecuteSequence (Action | Condition | Sequence) ===" | |
| try { | |
| $view4 = $db.OpenView("SELECT Action, Condition, Sequence FROM InstallExecuteSequence ORDER BY Sequence") | |
| $view4.Execute() | |
| while ($r = $view4.Fetch()) { | |
| $action = $r.StringData(1) | |
| $cond = $r.StringData(2) | |
| $seq = $r.IntegerData(3) | |
| Write-Host " [$seq] $action ${cond}" | |
| } | |
| } catch { Write-Host "Could not query InstallExecuteSequence table: $_" } | |
| - name: Install MSI directly | |
| shell: pwsh | |
| run: | | |
| $msi = Get-ChildItem -Filter "Scry2-*.msi" | Select-Object -First 1 | |
| $logFile = "$env:TEMP\scry2-msi-install.log" | |
| Write-Host "Installing: $($msi.Name)" | |
| $proc = Start-Process msiexec -ArgumentList "/i `"$($msi.FullName)`" /quiet /norestart /l*v `"$logFile`"" -Wait -PassThru | |
| Write-Host "msiexec exit code: $($proc.ExitCode)" | |
| if ($proc.ExitCode -ne 0 -and (Test-Path $logFile)) { | |
| Write-Host "=== First action with return value 3 ===" | |
| $lines = Get-Content $logFile | |
| for ($i = 0; $i -lt $lines.Count; $i++) { | |
| if ($lines[$i] -match "return value 3") { | |
| $start = [Math]::Max(0, $i - 20) | |
| for ($j = $start; $j -le $i; $j++) { Write-Host $lines[$j] } | |
| break | |
| } | |
| } | |
| } | |
| if ($proc.ExitCode -ne 0) { throw "msiexec exited with code $($proc.ExitCode)" } | |
| Write-Host "Installation completed" | |
| - name: Verify file layout | |
| shell: pwsh | |
| run: | | |
| # Must install to 64-bit Program Files — never Program Files (x86) | |
| $installDir = "${env:ProgramFiles}\Scry2" | |
| if (-not (Test-Path $installDir)) { | |
| # Detect wrong-arch install to surface the bug clearly | |
| if (Test-Path "${env:ProgramFiles(x86)}\Scry2") { | |
| throw "Scry2 installed to Program Files (x86) instead of Program Files — MSI missing -arch x64" | |
| } | |
| throw "Scry2 install directory not found at $installDir" | |
| } | |
| Write-Host "Install dir: $installDir" | |
| if (-not (Test-Path "$installDir\scry2-tray.exe")) { throw "scry2-tray.exe missing from $installDir" } | |
| if (-not (Test-Path "$installDir\bin\scry_2.bat")) { throw "bin\scry_2.bat missing from $installDir" } | |
| Write-Host "File layout OK: tray and release bin present" | |
| # Export for later steps | |
| "SCRY2_MSI_DIR=$installDir" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Verify registry autostart | |
| shell: pwsh | |
| run: | | |
| $val = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Scry2" -ErrorAction Stop | |
| if ($val.Scry2 -notlike "*scry2-tray.exe*") { throw "Autostart registry value incorrect: $($val.Scry2)" } | |
| Write-Host "Registry autostart OK: $($val.Scry2)" | |
| - name: Verify runtime eval | |
| shell: pwsh | |
| run: | | |
| $installDir = $env:SCRY2_MSI_DIR | |
| Write-Host "Testing runtime at: $installDir" | |
| # Use cmd /c to prevent scry_2.bat's exit from killing PowerShell | |
| # Eval starts full OTP app — use a simple expression and short timeout | |
| $proc = Start-Process -FilePath "cmd.exe" -ArgumentList "/c `"$installDir\bin\scry_2.bat`" eval `"IO.puts(:msi_ok)`"" -Wait -PassThru -WindowStyle Hidden -RedirectStandardOutput "$env:TEMP\eval-out.txt" -RedirectStandardError "$env:TEMP\eval-err.txt" | |
| if (Test-Path "$env:TEMP\eval-out.txt") { Get-Content "$env:TEMP\eval-out.txt" } | |
| if (Test-Path "$env:TEMP\eval-err.txt") { Get-Content "$env:TEMP\eval-err.txt" -Tail 10 } | |
| Write-Host "Eval exit code: $($proc.ExitCode)" | |
| - name: Kill processes under install dir before uninstall | |
| shell: pwsh | |
| run: | | |
| # The Verify-runtime-eval step starts erl.exe/beam.smp under $installDir. | |
| # Those processes may linger past the eval command's exit and hold files | |
| # open — which would cause rmdir /s /q to fail silently during uninstall. | |
| # Mirrors the defensive kill the zip install test does. | |
| $installDir = $env:SCRY2_MSI_DIR | |
| Write-Host "Killing processes rooted under $installDir" | |
| $killed = Get-Process | Where-Object { | |
| $_.Path -and $_.Path.StartsWith($installDir, [System.StringComparison]::OrdinalIgnoreCase) | |
| } | |
| foreach ($p in $killed) { | |
| Write-Host " killing $($p.Name) (pid $($p.Id)) — $($p.Path)" | |
| try { Stop-Process -Id $p.Id -Force -ErrorAction Stop } catch { Write-Host " failed: $_" } | |
| } | |
| Start-Sleep -Seconds 2 | |
| - name: Uninstall MSI | |
| shell: pwsh | |
| run: | | |
| $msi = Get-ChildItem -Filter "Scry2-*.msi" | Select-Object -First 1 | |
| $logFile = "$env:TEMP\scry2-msi-uninstall.log" | |
| Write-Host "Uninstalling: $($msi.Name)" | |
| $proc = Start-Process msiexec -ArgumentList "/x `"$($msi.FullName)`" /quiet /norestart /l*v `"$logFile`"" -Wait -PassThru | |
| if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 1605) { | |
| if (Test-Path $logFile) { Get-Content $logFile -Tail 30 } | |
| throw "msiexec uninstall exited with code $($proc.ExitCode)" | |
| } | |
| Write-Host "Uninstall completed" | |
| - name: Verify cleanup | |
| shell: pwsh | |
| run: | | |
| if ($env:SCRY2_MSI_DIR -and (Test-Path $env:SCRY2_MSI_DIR)) { throw "Install directory not removed" } | |
| $autostart = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Scry2" -ErrorAction SilentlyContinue | |
| if ($autostart) { throw "Autostart registry key not removed" } | |
| Write-Host "Cleanup OK: install dir gone, registry key removed" | |
| - name: Dump uninstall log on failure | |
| if: failure() | |
| shell: pwsh | |
| run: | | |
| $logFile = "$env:TEMP\scry2-msi-uninstall.log" | |
| if (Test-Path $logFile) { | |
| Write-Host "=== Lines referencing DeleteInstallDir / INSTALLFOLDER / rmdir / WixQuietExec ===" | |
| Select-String -Path $logFile -Pattern "DeleteInstallDir|INSTALLFOLDER|rmdir|WixQuietExec|WixRemove|CustomAction|Custom Action" | ForEach-Object { Write-Host $_.Line } | |
| Write-Host "" | |
| Write-Host "=== Full action sequence (Doing action / Action start) ===" | |
| Select-String -Path $logFile -Pattern "Doing action|Action start|return value" | ForEach-Object { Write-Host $_.Line } | |
| Write-Host "" | |
| Write-Host "=== Last 80 lines of uninstall log ===" | |
| Get-Content $logFile -Tail 80 | ForEach-Object { Write-Host $_ } | |
| } else { | |
| Write-Host "No uninstall log found at $logFile" | |
| } | |
| - name: Copy MSI logs for artifact upload | |
| if: failure() | |
| shell: pwsh | |
| run: | | |
| $installLog = "$env:TEMP\scry2-msi-install.log" | |
| $uninstallLog = "$env:TEMP\scry2-msi-uninstall.log" | |
| if (Test-Path $installLog) { | |
| Copy-Item $installLog "D:\a\scry-2\scry-2\msi-install.log" | |
| } | |
| if (Test-Path $uninstallLog) { | |
| Copy-Item $uninstallLog "D:\a\scry-2\scry-2\msi-uninstall.log" | |
| } | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: scry2-logs-msi | |
| path: | | |
| msi-install.log | |
| msi-uninstall.log | |
| ${{ env.APPDATA }}\scry_2\log\ | |
| if-no-files-found: ignore |