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>
82 lines
3.5 KiB
Markdown
82 lines
3.5 KiB
Markdown
# 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.conf` is untracked in the autotrader repo. It survives
|
|
that stack's pulls and deploys. It does **not** survive its
|
|
`bootstrap-tls.sh`, which clears `conf.d` — re-run `./deploy.sh` here if that
|
|
ever happens.
|
|
- `deploy.sh` reloads the shared nginx, so a mistake in the template would be a
|
|
mistake in front of the autotrader dashboard too. It runs `nginx -t` first 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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.
|