Skip to content

chore: release v0.47.7 #138

chore: release v0.47.7

chore: release v0.47.7 #138

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