Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions .github/workflows/install-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,31 @@ jobs:
- name: Add a sample session
run: python .github/scripts/write_sample_trace.py

- name: Seed an unrelated login startup entry
shell: powershell
run: |
$RunKey = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
if (-not (Test-Path -LiteralPath $RunKey)) {
New-Item -Path $RunKey | Out-Null
}
New-ItemProperty -LiteralPath $RunKey -Name "Token Meter CI sentinel" `
-PropertyType ExpandString -Value '%TEMP%\token-meter-ci-sentinel.exe' -Force | Out-Null

- name: Install
shell: powershell
run: |
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass `
-File scripts\install-windows.ps1 -BackendOnly -ReadinessTimeoutSeconds 600
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# Run twice to cover both initial registration and replacement of
# Token Meter's own existing value without changing another app's entry.
foreach ($Attempt in 1..2) {
& powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass `
-File scripts\install-windows.ps1 -BackendOnly -ReadinessTimeoutSeconds 600
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$RunKey = Get-Item -LiteralPath "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
if ($RunKey.GetValue("Token Meter CI sentinel", $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) -cne '%TEMP%\token-meter-ci-sentinel.exe' -or
$RunKey.GetValueKind("Token Meter CI sentinel") -ne [Microsoft.Win32.RegistryValueKind]::ExpandString) {
throw "installation $Attempt changed another application's startup entry"
}
}

- name: Check the installation
shell: powershell
Expand Down Expand Up @@ -351,6 +370,12 @@ jobs:
$StillServing = $true
try { $null = Invoke-RestMethod -Uri "http://127.0.0.1:8722/health" -TimeoutSec 3 } catch { $StillServing = $false }
if ($StillServing) { throw "the server still answers after uninstall" }
$Key = Get-Item -LiteralPath $RunKey
if ($Key.GetValue("Token Meter CI sentinel", $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) -cne '%TEMP%\token-meter-ci-sentinel.exe' -or
$Key.GetValueKind("Token Meter CI sentinel") -ne [Microsoft.Win32.RegistryValueKind]::ExpandString) {
throw "uninstall changed another application's startup entry"
}
Remove-ItemProperty -LiteralPath $RunKey -Name "Token Meter CI sentinel"
Write-Host "uninstall ok"

- name: Show server logs
Expand Down Expand Up @@ -406,6 +431,16 @@ jobs:
json.dump({"updates": {"enabled": True, "auto_install": False}}, fh)
PY

- name: Seed an unrelated login startup entry
shell: powershell
run: |
$RunKey = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
if (-not (Test-Path -LiteralPath $RunKey)) {
New-Item -Path $RunKey | Out-Null
}
New-ItemProperty -LiteralPath $RunKey -Name "Token Meter CI sentinel" `
-PropertyType ExpandString -Value '%TEMP%\token-meter-ci-sentinel.exe' -Force | Out-Null

- name: Install
shell: powershell
run: |
Expand Down Expand Up @@ -517,7 +552,12 @@ jobs:
$Leftovers = @(Get-ChildItem -LiteralPath (Split-Path -Parent $Runtime) -Directory |
Where-Object { $_.Name -like "runtime.*" })
if ($Leftovers.Count -gt 0) { throw "temporary runtime folders remain: $($Leftovers.Name -join ', ')" }
Write-Host "health ok; server restarted ($Before -> $After)"
$RunKey = Get-Item -LiteralPath "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run"
if ($RunKey.GetValue("Token Meter CI sentinel", $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) -cne '%TEMP%\token-meter-ci-sentinel.exe' -or
$RunKey.GetValueKind("Token Meter CI sentinel") -ne [Microsoft.Win32.RegistryValueKind]::ExpandString) {
throw "update changed another application's startup entry"
}
Write-Host "health ok; server restarted ($Before -> $After); unrelated startup entry preserved"

- name: Show update and server logs
if: failure()
Expand Down
5 changes: 4 additions & 1 deletion scripts/install-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,10 @@ try {
$PowerShellExe = $PowerShellCommand.Source
$StartScript = Join-Path $InstallRoot "scripts\start-token-meter.ps1"
$StartupCommand = "`"$PowerShellExe`" -NoLogo -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$StartScript`""
New-Item -Path $RunKey -Force | Out-Null
# Registry New-Item -Force recreates an existing key, discarding other apps' values.
if (-not (Test-Path -LiteralPath $RunKey)) {
New-Item -Path $RunKey | Out-Null
}
New-ItemProperty -LiteralPath $RunKey -Name $RunName -PropertyType String -Value $StartupCommand -Force | Out-Null

& $StartScript -ReadinessTimeoutSeconds $ReadinessTimeoutSeconds
Expand Down
10 changes: 10 additions & 0 deletions tests/contracts/test_windows_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ def test_manifest_is_the_only_windows_staging_inventory(self):
self.assertNotIn("$RequiredPaths", installer)
self.assertNotIn("$RuntimeDirectory", installer)

def test_windows_installer_never_forces_shared_startup_key_creation(self):
# Registry New-Item -Force recreates an existing key and discards other
# applications' startup values. Property-level -Force is safe and needed
# to replace only Token Meter's own startup command on reinstall.
installer = (ROOT / "scripts" / "install-windows.ps1").read_text(encoding="utf-8")
key_creations = re.findall(r"(?im)^\s*New-Item\s+[^\n]*\$RunKey\b[^\n]*", installer)
self.assertTrue(key_creations, "installer must handle a missing startup key")
for creation in key_creations:
self.assertNotRegex(creation, r"(?i)-Force\b", "shared startup key must never be recreated")

def test_windows_lifecycle_is_per_user_owned_and_local_only(self):
installer = (ROOT / "scripts" / "install-windows.ps1").read_text(encoding="utf-8")
starter = (ROOT / "scripts" / "start-token-meter.ps1").read_text(encoding="utf-8")
Expand Down
Loading