Let the extension update itself, and host the dashboard

Chrome has no update channel for an unpacked extension, so every pull left one
manual step: click reload on its card. The dashboard now publishes the version
its own checkout ships at /api/autobuyer/status, and the extension reloads
itself when it is behind — chrome.runtime.reload() re-reads the folder from
disk, which the update check has already pulled into. It only ever runs code
that is on disk, so a reload that changes nothing is reported and waited out
rather than repeated; a dashboard that is behind is ignored.

Also adds the deploy stack for running the dashboard on the droplet, next to
the autotrader one: it joins that nginx's network and renders a server block
into its conf.d rather than standing up a second ingress. AUTOFIRMER_DB moves
the SQLite file onto a volume, since the working directory in a container is
replaced on every deploy.

And waitFor gained `hidden`: an absent gate matched on DOM presence only, so a
modal that closes by going display:none could never satisfy it. The extension
grew a visibility probe for this — locate cannot answer it, because absent and
hidden are both errors there and a wait gate has to tell them apart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brandon Li
2026-08-31 12:30:59 -05:00
co-authored by Claude Opus 5
parent 1f4459985a
commit d1e8668440
18 changed files with 637 additions and 28 deletions
+16
View File
@@ -0,0 +1,16 @@
# HTTP only, for the window before a certificate exists: nginx will not start
# with an ssl_certificate that is not on disk yet, so the real config cannot go
# in until certbot has run. Rendered by bootstrap-tls.sh.
server {
listen 80;
listen [::]:80;
server_name ${FIRMER_HOST};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 503;
}
}
+52
View File
@@ -0,0 +1,52 @@
# 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;
}
}