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>
3.5 KiB
Hosting the dashboard on the droplet
The dashboard is the only piece that can live on a server. The clicker drives a
real mouse and keyboard and the extension runs inside your Chrome, so both stay
on the Windows machine — they just point at the droplet instead of at
localhost.
Windows box 159.223.129.19
clicker (runner.py) ──────┐
Chrome + extension ───────┴──HTTPS──> nginx ──> autofirmer:3000 ──> /data/autotrader.sqlite
This stack deliberately owns none of the ingress. The droplet already runs one
nginx on 80/443 for master.juicerroom.com and git.juicerroom.com, from
/opt/autotrader/deploy; this joins that network, and deploy.sh renders a
server block into that stack's nginx/conf.d. Two consequences worth knowing:
- The rendered
firmer.confis untracked in the autotrader repo. It survives that stack's pulls and deploys. It does not survive itsbootstrap-tls.sh, which clearsconf.d— re-run./deploy.shhere if that ever happens. deploy.shreloads the shared nginx, so a mistake in the template would be a mistake in front of the autotrader dashboard too. It runsnginx -tfirst and refuses to reload on a bad config, and it will not install the TLS server block until the certificate actually exists on disk.
First run
cp .env.example .env # set FIRMER_HOST
./bootstrap-tls.sh # DNS check, certificate, then deploy.sh
bootstrap-tls.sh uses the autotrader stack's certbot container and Let's
Encrypt account, and reads ACME_EMAIL from its .env rather than keeping a
second copy. The A record has to point at the droplet first, or the challenge
cannot be answered.
Shipping a change
git pull && ./deploy/deploy.sh
The build runs before anything is swapped, so a broken commit leaves the running
container serving. The database is on the autofirmer-data volume and is never
touched by a deploy.
Pointing the desktop at it
- Extension — open its popup and set the dashboard URL to
https://<FIRMER_HOST>. - Clicker —
python runner.py --api https://<FIRMER_HOST>, or edit the argument in the PM2 config that starts it.
Both keep working against http://localhost:3000 instead; nothing here forces
the hosted one.
No login
There is none, at the owner's request, and basic auth could not be bolted on
without breaking the two clients: the extension and the clicker poll
/api/autobuyer/* unattended with nowhere to put a password. Anyone who reaches
the host can queue runs, read the settings table — the 2captcha key included —
and spend money. If that changes, the options are an IP allowlist in the nginx
template, or a shared secret that both clients learn to send.
Updating the extension
The extension can no longer be updated by this stack alone, and it is worth
being clear about why. Chrome has no update channel for an unpacked extension:
it reads the folder off disk once and never looks again. What the dashboard
does is publish the version its own checkout ships, at
/api/autobuyer/status. The extension compares that against its own manifest
and calls chrome.runtime.reload(), which re-reads the folder from disk.
So the files still arrive the way they always did — scripts/update-check.mjs
pulling on the Windows box every five minutes. If the droplet is ahead of that
pull, the extension reloads once, notices nothing changed, and waits instead of
looping; the popup says as much.