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
@@ -4,7 +4,7 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${FIRMER_HOST};
|
||||
server_name ${EXT_HOST};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
@@ -0,0 +1,45 @@
|
||||
# The extension update server — rendered into the autotrader stack's nginx/conf.d
|
||||
# by deploy.sh. Edit the template, not the rendered file.
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${EXT_HOST};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name ${EXT_HOST};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${EXT_HOST}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${EXT_HOST}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
|
||||
# Two public files: the update manifest and the signed CRX. Both are meant to
|
||||
# be readable by anyone — Chrome fetches them with no cookies and no way to
|
||||
# authenticate, which is why there is no auth here. The CRX is signed, and
|
||||
# carries no credentials; the private key stays on the droplet.
|
||||
location / {
|
||||
proxy_pass http://autofirmer-updates/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
# AutoFirmer dashboard — rendered into the autotrader stack's nginx/conf.d by
|
||||
# deploy.sh. Edit the template, not the rendered file.
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name ${FIRMER_HOST};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
server_name ${FIRMER_HOST};
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/${FIRMER_HOST}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${FIRMER_HOST}/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 1d;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
|
||||
# A capture posts a whole page's HTML, which runs to megabytes on the firm
|
||||
# dashboards. nginx's 1m default would reject those with a 413.
|
||||
client_max_body_size 32m;
|
||||
|
||||
# No basic auth, at the owner's request and because it could not work here
|
||||
# anyway: the extension and the clicker poll /api/autobuyer/* unattended and
|
||||
# have nowhere to put a password. Anyone who reaches this host can queue
|
||||
# runs, read the settings table including the 2captcha key, and spend money.
|
||||
location / {
|
||||
proxy_pass http://autofirmer:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Inside the little file-server container. Not the public-facing config — that
|
||||
# is ext.conf.template, rendered into the shared nginx.
|
||||
server {
|
||||
listen 80;
|
||||
root /srv;
|
||||
|
||||
# Replaces the whole MIME table, which is fine when the whole site is two
|
||||
# files. Chrome is happy to take a CRX as octet-stream, but the updater logs
|
||||
# are much easier to read when the type is right.
|
||||
types {
|
||||
application/x-chrome-extension crx;
|
||||
text/xml xml;
|
||||
}
|
||||
default_type application/octet-stream;
|
||||
|
||||
# The update manifest is the thing Chrome polls to notice a release; a cached
|
||||
# copy is a release that does not land. The CRX itself is named by version
|
||||
# inside that manifest, so caching it would be safe — but it is one small
|
||||
# file fetched once per update, and a single rule is easier to trust.
|
||||
add_header Cache-Control "no-store" always;
|
||||
|
||||
location / {
|
||||
autoindex off;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user