# miniquota deployment — verified transcript (2026-07-05)

Raw sequence of what actually shipped, why each step was needed, and the most useful debugging insights. Read this once when adding a new provider; it's much faster than rediscovering them.

## What was built

| Path | Role |
|---|---|
| `/root/miniquota/` | project root (single source) |
| `/root/miniquota/app.py` | ~400-line FastAPI backend, 2 providers (MiniMax, OpenCode Go), HTML UI |
| `/root/miniquota/config.json` | chmod 600, real credentials, 1 MiniMax + 2 OpenCode accounts |
| `/root/miniquota/static/index.html` | single-file UI: dark theme, cards, auto-refresh 30s |
| `/root/miniquota/venv/` | Python 3.12.3 venv, fastapi 0.139 + httpx 0.28 + uvicorn 0.50 + pydantic 2.13 |

| Public URL | Role |
|---|---|
| `https://quota.spannerjun.top` | the UI |
| `https://quota.spannerjun.top/api/health` | `{status:ok}` |
| `https://quota.spannerjun.top/api/MiniMax/quota` | MiniMax Coding Plan windows |
| `https://quota.spannerjun.top/api/opencode_go/quota` | OpenCode Go rolling/weekly/monthly |
| `https://quota.spannerjun.top/api/config` | masked credentials + status |

## Why a separate project instead of upstream QuotaHub

User explicitly said "use opencode to modify this project" then later "不是在原有基础上去改 — 分离出一个项目,我直接 python 运行就行的那种". The intent: avoid the upstream Docker pipeline entirely.

What followed: 6+ build attempts against buildkit stage network restrictions, all failed. The pivot to a venv + single-file Python was a direct response to that user preference.

See `nginx-reverse-proxy-skill` notes about why buildkit stage network fails, and **don't try again on this host** — pip with a CN mirror is the only thing that works.

## Three real bugs, all fixed during the session

### Bug 1 — MiniMax `*_usage_count` field is misleading

The upstream minimax-usage TypeScript code interprets `current_interval_usage_count = 3` as "3 remaining" because `total = 3`. We copied this logic initially and produced `video: 0/3 remaining` (wrong — should be `0% remaining since 100% used`).

**Fix:** Trust `current_interval_remaining_percent` (integer 0-100). Compute `used = 100 - remaining`.

**Lesson:** if upstream docs and a confident-sounding field name conflict, do a single sanity-check API call and inspect raw output before building the wrapper. The cost of one extra probe is much less than the cost of a "looks-OK-but-is-wrong" wrapper that ships to production.

### Bug 2 — OpenCode alt-regex group order

The RSC payload has `resetInSec` and `usagePercent` in **either order**. We wrote two regex patterns (primary and alt) and tried `try: pct=float(g1); reset=int(float(g2)) except: pct=float(g2); reset=int(float(g1))`. Bug: when alt matched, group 1 was reset (looks like a number) and group 2 was pct (also a number) — `try/except` doesn't help because **neither conversion fails**. The decode always produced `pct = reset_in_sec / 10000` ≈ `100%` (clamped). Symptom: every account showed 100% Weekly + Monthly.

**Fix:** Track which regex matched with a boolean flag and decode groups in order.

**Lesson:** when you have two parsers for one block of data, the parsing logic must also encode **which parser matched** — `try/except` on identical-type conversions is not enough. Add `used_alt = bool(...)` or `matcher = primary or alt` and pass it forward.

### Bug 3 — list comprehension generated the wrong keyword

`RE_WINDOWS_ALT = [re.compile(rf"{label.split()[0].lower()}Usage:...") for label, _ in RE_WINDOWS]` — for `"5h Rolling"`, the keyword became `5h` → regex `5hUsage:...`. Never matches anything (real prefix is `rollingUsage:`).

**Fix:** Write each alt pattern explicitly.

**Lesson:** Don't derive regex keywords from human labels. The labels are user-facing copy; the prefix in upstream payload is a separate vocabulary. If you must derive, write a `LABEL_TO_PREFIX = {...}` lookup, not a string transform.

## Three daemon-level incidents

### a) `acme.sh --install-cert` half-fail during quota.spannerjun.top rollout

Sequence:
1. `acme.sh --issue` succeeded (cert issued).
2. `acme.sh --install-cert` exited non-zero (state-dir mismatch — `~/.acme.sh/quota.spannerjun.top_ecc/quota.spannerjun.top.cer` didn't exist because `--issue` had run in `--test` mode first under a separate staging path).
3. `acme.sh` truncated `/etc/nginx/ssl/quota.spannerjun.top.crt` to 0 bytes before exiting.
4. `systemctl reload nginx` ran as reloadcmd → nginx emitted `SSL_CTX_use_PrivateKey_file() failed`.
5. `systemctl status nginx` showed `Active: active (running)`. **Misleading** — master was alive, but the failing vhost had no workers reading it.

**Fix:** regenerate the placeholder cert with `openssl req -x509 ... -subj "/CN=quota.spannerjun.top"`, then `acme.sh --install-cert ...` again. Documented as a pitfall in `nginx-reverse-proxy-skill`.

### b) sandbox blocks `pkill -f "app.py"`

Running `pkill -f "miniquota|app.py"` from a regular `terminal()` call returns exit -15 / blocked by the sandbox even after approval. Symptom is misleading because the command is "obviously safe". The reliable kill sequence is `process(action="kill")` against the session_id returned by `terminal(background=true)`. There is no shell-level workaround.

### c) `nginx -s reload` exit code ≠ "did reload succeed"

This ties into (a). After `nginx -t` showed `SSL_CTX_use_PrivateKey_file() failed`, `nginx -s reload` printed `[notice] signal process started` and exited 0. Confirmed: `nginx -s reload` returns 0 even when the new config is broken, because the master PID accepted the signal but workers discovered the failure later. **Always end your reload script with `nginx -t && systemctl reload nginx`** so the test pass is a hard precondition.

## Public-facing scanner probes within ~30 minutes of cert issue

After quota.spannerjun.top's LE cert was issued, IPs `144.172.116.30` (US), `193.32.248.185` (Russia), `182.200.140.32` (China) hit `/api/env`, `/.env`, `/.env.production`, `/js/config.js`, `/settings.js`. All 404 except `/api/config` (200 with masked creds). This is automated scanning triggered by LE cert issuance being a public event.

**Lesson:** any new subdomain with a fresh cert gets scanned quickly. Don't expose:
- `/api/env` style endpoints
- Verbose `/api/health` (the upstream `{"status":"ok"}` is fine; don't include version banners)
- Internal config that even masked would reveal which providers are in use
