Ship the extension as a signed CRX from our own update server
The dashboard-hosting stack from the previous commit is gone; it was answering a question nobody asked. What was actually needed is Chrome's real update channel, which an unpacked extension does not have and cannot be given. So the extension is now signed into a CRX with an update manifest beside it, served from the droplet, and installed on the trading machines by policy (ExtensionInstallForcelist). From there Chrome installs it, updates it on its own schedule, and puts it back if it goes missing. The manifest gained the `key` that fixes the extension id, so an unpacked development load keeps the same identity as the published build. The self-update check now branches on how it was installed: policy installs ask Chrome to check the update server now rather than waiting out its five-hour cycle, and unpacked loads keep the reload-from-disk path. chrome.management .getSelf() tells the two apart and needs no permission to do it. The signing key lives at /opt/autofirmer-updates/key.pem on the droplet and nowhere else. It is the extension's identity: a different key is a different extension, and no browser would take it as an update. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ece6aab9cf
commit
035f93d220
@@ -0,0 +1,101 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Install the AutoFirmer extension from our own update server, via policy.
|
||||
|
||||
.DESCRIPTION
|
||||
Chrome will not side-load an extension on Windows, and it will not update an
|
||||
unpacked one. The supported way to run an extension you host yourself is an
|
||||
enterprise policy: ExtensionInstallForcelist names the extension id and the
|
||||
update manifest, and from then on Chrome installs it, keeps it updated on its
|
||||
own schedule, and re-installs it if it goes missing.
|
||||
|
||||
Needs an elevated PowerShell — the policy lives under HKLM.
|
||||
|
||||
After this runs:
|
||||
* Remove the unpacked copy from chrome://extensions. It has the same id
|
||||
now, and Chrome will not load both.
|
||||
* Restart Chrome, or open chrome://policy and press "Reload policies".
|
||||
* A force-installed extension cannot be disabled or removed from the
|
||||
extensions page. Re-run this with -Remove to take it off.
|
||||
|
||||
.EXAMPLE
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\install-extension-policy.ps1
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('Chrome', 'Edge', 'Brave')]
|
||||
[string]$Browser = 'Chrome',
|
||||
|
||||
# Derived from the signing key on the droplet. It only changes if the key does.
|
||||
[string]$Id = 'lkjbblagjjbldlkeaaffhiopdlhniaan',
|
||||
|
||||
[string]$UpdateUrl = 'https://ext.juicerroom.com/updates.xml',
|
||||
|
||||
[switch]$Remove
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$roots = @{
|
||||
Chrome = 'HKLM:\SOFTWARE\Policies\Google\Chrome'
|
||||
Edge = 'HKLM:\SOFTWARE\Policies\Microsoft\Edge'
|
||||
Brave = 'HKLM:\SOFTWARE\Policies\BraveSoftware\Brave'
|
||||
}
|
||||
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
if (-not ([Security.Principal.WindowsPrincipal]$identity).IsInRole(
|
||||
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this from an elevated PowerShell — the policy key is under HKLM.'
|
||||
}
|
||||
|
||||
$key = Join-Path $roots[$Browser] 'ExtensionInstallForcelist'
|
||||
$entry = "$Id;$UpdateUrl"
|
||||
|
||||
if ($Remove) {
|
||||
if (-not (Test-Path $key)) { Write-Host "Nothing to remove — no forcelist for $Browser."; exit 0 }
|
||||
$props = Get-ItemProperty $key
|
||||
$found = $false
|
||||
foreach ($name in ($props.PSObject.Properties.Name | Where-Object { $_ -notlike 'PS*' })) {
|
||||
if ($props.$name -like "$Id;*") {
|
||||
Remove-ItemProperty -Path $key -Name $name
|
||||
Write-Host "Removed $name ($($props.$name))"
|
||||
$found = $true
|
||||
}
|
||||
}
|
||||
if (-not $found) { Write-Host "Nothing to remove — $Id was not in the $Browser forcelist." }
|
||||
Write-Host "`nRestart $Browser to let the change take effect."
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not (Test-Path $key)) { New-Item -Path $key -Force | Out-Null }
|
||||
|
||||
# The forcelist is a list expressed as numbered values, so adding to it means
|
||||
# finding a free number rather than overwriting whatever is already there —
|
||||
# another extension may well be in the list.
|
||||
$props = Get-ItemProperty $key
|
||||
$existing = $props.PSObject.Properties |
|
||||
Where-Object { $_.Name -notlike 'PS*' -and $_.Value -like "$Id;*" }
|
||||
|
||||
if ($existing) {
|
||||
Set-ItemProperty -Path $key -Name $existing.Name -Value $entry
|
||||
Write-Host "Updated $Browser forcelist entry $($existing.Name):"
|
||||
} else {
|
||||
$used = $props.PSObject.Properties.Name |
|
||||
Where-Object { $_ -match '^\d+$' } | ForEach-Object { [int]$_ }
|
||||
$slot = if ($used) { (($used | Measure-Object -Maximum).Maximum + 1) } else { 1 }
|
||||
New-ItemProperty -Path $key -Name "$slot" -Value $entry -PropertyType String -Force | Out-Null
|
||||
Write-Host "Added $Browser forcelist entry ${slot}:"
|
||||
}
|
||||
|
||||
Write-Host " $entry"
|
||||
Write-Host @"
|
||||
|
||||
Next:
|
||||
1. Remove the unpacked copy from chrome://extensions (same id — both cannot load).
|
||||
2. Restart $Browser, or open chrome://policy and press "Reload policies".
|
||||
3. Check chrome://extensions: the card should say "Installed by enterprise policy".
|
||||
|
||||
From here $Browser updates it on its own. The extension also asks for a check as
|
||||
soon as the dashboard reports a newer version, so a release normally lands in
|
||||
seconds rather than waiting out Chrome's own cycle.
|
||||
"@
|
||||
Reference in New Issue
Block a user