run in docker, and work through more than one account

Both taken from TheNetsky/Microsoft-Rewards-Script, which packages a container
and handles several accounts. Approach only: that project is GPL-3.0 and this
one MIT, so no code crosses over.

**Accounts.** Rewards is per Microsoft account and the browser profile holds
the sign-in, so an account here is a profile directory. REWARDS_ACCOUNTS takes
a comma separated list and gives each its own directory under the configured
one. They run in sequence, and a profile that will not start is reported and
skipped rather than ending the run. Left unset, a run uses the single profile
exactly as before.

Names are validated rather than trusted: they become directory names, so
"../escape" is refused instead of quietly writing outside data-dir.

**Docker.** The image carries only what main.py actually reaches, selenium and
numpy. pygetwindow, keyboard, matplotlib and pygame are used solely by the
recording and visualisation scripts, and two of those are Windows-only, so
none of them belong in a container. msedgedriver is pinned at build time to
the Edge the image installed rather than to latest, which drifts from it
between releases.

QUERY_SOURCE defaults to trends in the image, so a container needs no Ollama
account and no model download at all.

That default turned out to require a fix. queries.py imported llm_utils at
module scope, which imports ollama, so a trends-only install still needed the
ollama package: exactly what running in a minimal image is good at exposing.
The import is now made inside the llm branch, and the wordlist fallback reads
nouns.txt directly rather than borrowing llm_utils.get_random_noun.

REWARDS_HEADLESS drives the headless flags. The window size is set explicitly
because the pointer code works in viewport coordinates and the default
headless window is small enough to put cards out of reach, which is the
MoveTargetOutOfBoundsException from #19. Verified on the host that
move_to_element and human_like_click both work headless before relying on it.

Verified in the built image: Edge 151.0.4129.107 with a driver of exactly the
same build, the trends feed reachable from inside, Edge driven to bing.com and
rewards.bing.com at 1920x1080, and REWARDS_ACCOUNTS producing separate profile
directories with traversal refused.
This commit is contained in:
Ethan Stoner
2026-08-26 15:13:02 -07:00
parent af03afcc6d
commit bfe30dd9a0
8 changed files with 335 additions and 16 deletions
+54
View File
@@ -0,0 +1,54 @@
# Runs the bot without installing Edge, a driver or Python on the host.
#
# The image carries only what main.py actually reaches: selenium and numpy.
# pygetwindow, keyboard, matplotlib and pygame are used solely by the
# recording and visualisation scripts, which are developer tools rather than
# part of a run, and two of them are Windows-only.
#
# QUERY_SOURCE defaults to trends here so a container needs no Ollama account
# and no model download. Set it to llm and point OLLAMA_HOST at a reachable
# host to use a model instead.
FROM python:3.12-slim-bookworm
ENV DEBIAN_FRONTEND=noninteractive
# Edge, from Microsoft's own repository.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl gnupg unzip fonts-liberation \
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /usr/share/keyrings/microsoft.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/edge stable main" \
> /etc/apt/sources.list.d/microsoft-edge.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends microsoft-edge-stable \
&& rm -rf /var/lib/apt/lists/*
# The driver has to match the browser build, so it is pinned to whatever Edge
# the layer above installed rather than to "latest", which drifts apart from it
# between releases.
RUN EDGE_VERSION="$(microsoft-edge --version | awk '{print $3}')" \
&& curl -fsSL -o /tmp/edgedriver.zip \
"https://msedgedriver.microsoft.com/${EDGE_VERSION}/edgedriver_linux64.zip" \
&& unzip -j /tmp/edgedriver.zip msedgedriver -d /usr/local/bin \
&& chmod +x /usr/local/bin/msedgedriver \
&& rm /tmp/edgedriver.zip \
&& msedgedriver --version
WORKDIR /app
RUN pip install --no-cache-dir "selenium>=4.46.0,<5.0.0" "numpy"
COPY src/ ./src/
COPY nouns.txt ./
# Headless because there is no display, and trends because there is no model.
ENV REWARDS_HEADLESS=1 \
QUERY_SOURCE=trends \
PYTHONUNBUFFERED=1
# Sign-in lives here, so it has to outlive the container.
VOLUME ["/app/data-dir"]
CMD ["python", "src/main.py"]