Merge the wilm work, act on the five-agent review, verify the launch-path policy on Windows
This commit is contained in:
@@ -41,6 +41,14 @@ from .routes import endpoints_bp
|
||||
STAGED_AGENT_RELPATH = os.path.join('siteagent', 'shopdbagent.exe')
|
||||
STAGED_TRAY_RELPATH = os.path.join('siteagent', 'shopdb-endpoint-tray.exe')
|
||||
|
||||
# The standalone uninstaller lives in the repo tree (not staged per-instance),
|
||||
# so it ships in every bundle. Resolve it relative to this file:
|
||||
# plugins/endpoints/api/siteagent.py -> repo root is three levels up.
|
||||
UNINSTALLER_PATH = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__))))),
|
||||
'scripts', 'uninstall-shopdbagent.ps1')
|
||||
|
||||
# Public TLS certificate the agent must trust when this deployment terminates
|
||||
# TLS itself with a self-signed or otherwise untrusted certificate. We can only
|
||||
# auto-bundle when THIS app owns the cert file (the dev topology, where gunicorn
|
||||
@@ -144,6 +152,12 @@ FILES
|
||||
appsettings.json Non-secret runtime config (cadence, monitors,
|
||||
launch paths). No credentials.
|
||||
install.ps1 Provisions the credentials and installs the service.
|
||||
uninstall-shopdbagent.ps1
|
||||
Standalone uninstaller. Run elevated to reverse the
|
||||
install: stops and deletes the service, kills agent
|
||||
and tray processes, removes the tray autostart Run
|
||||
key, the Start Menu shortcut, and the credential
|
||||
hive, and deletes the install directory.
|
||||
server.crt Server TLS certificate (present only for self-signed
|
||||
or internal-CA deployments, either auto-detected or
|
||||
uploaded by the operator). install.ps1 imports it
|
||||
@@ -153,10 +167,16 @@ FILES
|
||||
USAGE
|
||||
1. Copy this whole folder to the target endpoint.
|
||||
2. Right-click install.ps1 and Run with PowerShell as administrator. It
|
||||
writes the server URL and collector key to HKLM\\SOFTWARE\\GE\\ShopDB,
|
||||
copies the agent into Program Files, and starts the service.
|
||||
writes the server URL and collector key to HKLM\\SOFTWARE\\GE\\ShopDB,
|
||||
copies the agent into Program Files, and starts the service. It also
|
||||
registers the tray to autostart for every user at logon and adds a
|
||||
"ShopDB Agent" Start Menu shortcut for manual relaunch.
|
||||
|
||||
SECURITY
|
||||
UNINSTALL
|
||||
Run uninstall-shopdbagent.ps1 from an elevated PowerShell to fully remove the
|
||||
agent, tray autostart, Start Menu shortcut, and credentials.
|
||||
|
||||
SECURITY
|
||||
install.ps1 carries a live collector key in its default -CollectorKey value.
|
||||
Treat this package as a secret. The key is not recoverable from the ShopDB
|
||||
server after it was generated; regenerate the key in the UI to revoke it.
|
||||
@@ -248,7 +268,7 @@ Write-Host "ServiceName : $ServiceName"
|
||||
# 0. Stop and remove any previous install so the file copy is a clean overwrite.
|
||||
$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($existing) {{
|
||||
Write-Host "[0/4] Removing existing service $ServiceName ..."
|
||||
Write-Host "[0/6] Removing existing service $ServiceName ..."
|
||||
if ($existing.Status -ne "Stopped") {{
|
||||
try {{ Stop-Service -Name $ServiceName -Force -ErrorAction Stop }}
|
||||
catch {{ Write-Warning "could not stop service: $($_.Exception.Message)" }}
|
||||
@@ -264,7 +284,7 @@ foreach ($name in @("shopdbagent", $trayBase)) {{
|
||||
}}
|
||||
|
||||
# 1. Provision registry credentials (both 64-bit and 32-bit views).
|
||||
Write-Host "[1/4] Provisioning registry HKLM:\\SOFTWARE\\GE\\ShopDB (Registry64 + Registry32) ..."
|
||||
Write-Host "[1/6] Provisioning registry HKLM:\\SOFTWARE\\GE\\ShopDB (Registry64 + Registry32) ..."
|
||||
Set-HklmValueBothViews -SubKey "SOFTWARE\\GE\\ShopDB" -Name "BaseUrl" -Value $BaseUrl
|
||||
Set-HklmValueBothViews -SubKey "SOFTWARE\\GE\\ShopDB" -Name "CollectorKey" -Value $CollectorKey
|
||||
|
||||
@@ -288,7 +308,7 @@ if (-not [string]::IsNullOrWhiteSpace($CertFileName)) {{
|
||||
}}
|
||||
|
||||
# 2. Copy agent files to the install directory.
|
||||
Write-Host "[2/4] Copying agent files to $InstallDir ..."
|
||||
Write-Host "[2/6] Copying agent files to $InstallDir ..."
|
||||
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
||||
Get-ChildItem -Path $sourceDir -File | ForEach-Object {{
|
||||
try {{ Unblock-File -Path $_.FullName -ErrorAction Stop }} catch {{ }}
|
||||
@@ -306,17 +326,118 @@ if (-not $NoTray) {{
|
||||
$traySource = Join-Path $sourceDir $TrayExeName
|
||||
if (Test-Path $traySource) {{
|
||||
Copy-Item -Path $traySource -Destination $InstallDir -Force
|
||||
$installedTray = Join-Path $InstallDir $TrayExeName
|
||||
|
||||
# Autostart the tray for every user at logon via the HKLM Run key (both
|
||||
# 64-bit and 32-bit views, matching the credential hive). This is the
|
||||
# reliable launch guarantee: a plain Start Menu shortcut does not
|
||||
# autostart, and the SYSTEM service cannot cleanly spawn into the
|
||||
# interactive session. The tray itself is single-instance, so a stray
|
||||
# second launch is harmless.
|
||||
Set-HklmValueBothViews -SubKey "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" -Name "ShopDbEndpointTray" -Value ('"' + $installedTray + '"')
|
||||
|
||||
# Export the branded icon from the tray exe so the shortcut shows the
|
||||
# ShopDB brand instead of the generic exe icon. The tray draws its icon
|
||||
# at runtime (no embedded Win32 icon), so we ask it to write an .ico into
|
||||
# the install dir and point the shortcut at it.
|
||||
$iconPath = Join-Path $InstallDir "shopdb-agent.ico"
|
||||
try {{ & $installedTray --export-icon $iconPath | Out-Null }}
|
||||
catch {{ Write-Warning "could not export shortcut icon: $($_.Exception.Message)" }}
|
||||
|
||||
# Start Menu shortcut so operators can relaunch the tray on demand. This
|
||||
# is discoverability only, not autostart. Written to the machine-wide
|
||||
# (All Users) Programs folder.
|
||||
try {{
|
||||
$commonPrograms = [Environment]::GetFolderPath([Environment+SpecialFolder]::CommonPrograms)
|
||||
$shortcutPath = Join-Path $commonPrograms "ShopDB Agent.lnk"
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
$shortcut = $shell.CreateShortcut($shortcutPath)
|
||||
$shortcut.TargetPath = $installedTray
|
||||
$shortcut.WorkingDirectory = $InstallDir
|
||||
$shortcut.Description = "ShopDB Endpoint Agent tray"
|
||||
if (Test-Path $iconPath) {{ $shortcut.IconLocation = "$iconPath,0" }}
|
||||
$shortcut.Save()
|
||||
Write-Host " created Start Menu shortcut: $shortcutPath"
|
||||
}}
|
||||
catch {{ Write-Warning "could not create Start Menu shortcut: $($_.Exception.Message)" }}
|
||||
|
||||
# Launch the tray once now so it appears without waiting for the next
|
||||
# logon. Single-instance guard makes a duplicate launch a no-op.
|
||||
try {{ Start-Process -FilePath $installedTray -WorkingDirectory $InstallDir }}
|
||||
catch {{ Write-Warning "could not start tray: $($_.Exception.Message)" }}
|
||||
}}
|
||||
}}
|
||||
|
||||
# 3. Install and start the service. The agent supports its own service install.
|
||||
Write-Host "[3/4] Installing and starting the service ..."
|
||||
Write-Host "[3/6] Installing and starting the service ..."
|
||||
$installedAgent = Join-Path $InstallDir "shopdbagent.exe"
|
||||
$binPath = '"' + $installedAgent + '"'
|
||||
sc.exe create $ServiceName binPath= $binPath start= auto DisplayName= "ShopDB Endpoint Agent" | Out-Null
|
||||
sc.exe start $ServiceName | Out-Null
|
||||
|
||||
Write-Host "[4/4] Done." -ForegroundColor Green
|
||||
# 4. Confirm the service (and tray, when installed) actually reached a running
|
||||
# state before we destroy the installer. sc.exe start returns before the service
|
||||
# is up, and a failed start would otherwise leave a half-installed endpoint with
|
||||
# the secret-bearing install.ps1 already deleted. Poll with a short backoff and
|
||||
# retry the start a few times; only proceed to cleanup once healthy.
|
||||
Write-Host "[4/6] Verifying agent runtime ..."
|
||||
$serviceOk = $false
|
||||
for ($attempt = 1; $attempt -le 5; $attempt++) {{
|
||||
Start-Sleep -Seconds 3
|
||||
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
|
||||
if ($svc -and $svc.Status -eq "Running") {{ $serviceOk = $true; break }}
|
||||
Write-Host " service not running yet (attempt $attempt/5); retrying start ..."
|
||||
try {{ Start-Service -Name $ServiceName -ErrorAction Stop }} catch {{ }}
|
||||
}}
|
||||
if (-not $serviceOk) {{
|
||||
throw "Service $ServiceName did not reach Running after 5 attempts. Leaving install.ps1 in place for diagnosis; re-run once resolved."
|
||||
}}
|
||||
Write-Host " service is running." -ForegroundColor Green
|
||||
|
||||
$trayBase2 = [System.IO.Path]::GetFileNameWithoutExtension($TrayExeName)
|
||||
if (-not $NoTray -and (Test-Path (Join-Path $InstallDir $TrayExeName))) {{
|
||||
$trayOk = $false
|
||||
for ($attempt = 1; $attempt -le 5; $attempt++) {{
|
||||
if (Get-Process -Name $trayBase2 -ErrorAction SilentlyContinue) {{ $trayOk = $true; break }}
|
||||
Write-Host " tray not running yet (attempt $attempt/5); relaunching ..."
|
||||
try {{ Start-Process -FilePath (Join-Path $InstallDir $TrayExeName) -WorkingDirectory $InstallDir }} catch {{ }}
|
||||
Start-Sleep -Seconds 2
|
||||
}}
|
||||
if ($trayOk) {{ Write-Host " tray is running." -ForegroundColor Green }}
|
||||
else {{ Write-Warning "tray did not confirm running; it will retry at next logon via the Run key." }}
|
||||
}}
|
||||
|
||||
# 5. Preserve the install artifacts (minus the secret-bearing install.ps1) under
|
||||
# ProgramData so the uninstaller and appsettings/cert are available later, then
|
||||
# scrub install.ps1 from wherever it was run. install.ps1 carries the live
|
||||
# collector key in its default parameter, so it must not linger on the endpoint.
|
||||
Write-Host "[5/6] Archiving install artifacts and removing install.ps1 ..."
|
||||
$dataDir = Join-Path $env:ProgramData "ShopDB\\EndpointAgent"
|
||||
New-Item -ItemType Directory -Force -Path $dataDir | Out-Null
|
||||
foreach ($name in @("uninstall-shopdbagent.ps1", "appsettings.json", "README.txt", $CertFileName)) {{
|
||||
if ([string]::IsNullOrWhiteSpace($name)) {{ continue }}
|
||||
$src = Join-Path $sourceDir $name
|
||||
if (Test-Path $src) {{
|
||||
try {{ Copy-Item -Path $src -Destination $dataDir -Force }}
|
||||
catch {{ Write-Warning "could not archive $name : $($_.Exception.Message)" }}
|
||||
}}
|
||||
}}
|
||||
$uninstaller = Join-Path $dataDir "uninstall-shopdbagent.ps1"
|
||||
|
||||
$installSelf = Join-Path $sourceDir "install.ps1"
|
||||
if (Test-Path $installSelf) {{
|
||||
try {{ Remove-Item -Path $installSelf -Force -ErrorAction Stop; Write-Host " removed install.ps1 (carried the collector key)." }}
|
||||
catch {{ Write-Warning "could not remove install.ps1 - delete it manually, it contains the collector key: $($_.Exception.Message)" }}
|
||||
}}
|
||||
|
||||
Write-Host "[6/6] Done." -ForegroundColor Green
|
||||
if (Test-Path $uninstaller) {{
|
||||
Write-Host "Uninstaller: $uninstaller" -ForegroundColor Cyan
|
||||
Write-Host " To remove the agent, run that script from an elevated PowerShell."
|
||||
}}
|
||||
else {{
|
||||
Write-Warning "uninstaller was not bundled; fetch uninstall-shopdbagent.ps1 from the ShopDB repo scripts/ folder if needed."
|
||||
}}
|
||||
""".format(
|
||||
baseurl=baseurl_literal,
|
||||
collectorkey=collectorkey_literal,
|
||||
@@ -421,6 +542,9 @@ def package_siteagent():
|
||||
cadenceseconds, certfilename))
|
||||
archive.writestr('README.txt', _README)
|
||||
|
||||
if os.path.isfile(UNINSTALLER_PATH):
|
||||
archive.write(UNINSTALLER_PATH, 'uninstall-shopdbagent.ps1')
|
||||
|
||||
agent = _staged_path(STAGED_AGENT_RELPATH)
|
||||
if os.path.isfile(agent):
|
||||
archive.write(agent, 'shopdbagent.exe')
|
||||
|
||||
Reference in New Issue
Block a user