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:
Brandon Li
2026-08-31 12:50:01 -05:00
co-authored by Claude Opus 5
parent ece6aab9cf
commit 035f93d220
20 changed files with 443 additions and 288 deletions
+11 -18
View File
@@ -1,30 +1,23 @@
# The dashboard, alongside the autotrader stack rather than instead of it: the
# droplet already runs one nginx on 80/443, and this joins that network so the
# same ingress can proxy to it. Nothing here publishes a host port.
name: autofirmer
# A file server and nothing else: two static files, no application, no database.
# It joins the autotrader stack's network so the nginx already on 80/443 can
# proxy to it — this publishes no host port of its own.
name: autofirmer-updates
services:
autofirmer:
build:
context: ..
dockerfile: Dockerfile
image: autofirmer:local
container_name: autofirmer
updates:
image: nginx:alpine
container_name: autofirmer-updates
restart: unless-stopped
environment:
AUTOFIRMER_DB: /data/autotrader.sqlite
volumes:
- autofirmer-data:/data
- ${UPDATES_DIR:-/opt/autofirmer-updates}/public:/srv:ro
- ./nginx/updates-static.conf:/etc/nginx/conf.d/default.conf:ro
expose:
- "3000"
- "80"
networks: [web]
networks:
# Created by the autotrader stack. `external` so this compose file never tries
# to own it — bringing this stack down must not take the ingress with it.
# to own it — bringing this down must not take the ingress with it.
web:
name: ${SHARED_NETWORK:-autotrader-vps_web}
external: true
volumes:
autofirmer-data: