# 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://`. - **Clicker** — `python runner.py --api https://`, 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.