mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 01:06:17 +00:00
feat: refactor Docker setup for built-in cron scheduling and improved entrypoint handling
This commit is contained in:
66
.dockerignore
Normal file
66
.dockerignore
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
tests/
|
||||||
|
|
||||||
|
# Environment and config
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# Documentation (not needed in image)
|
||||||
|
docs/
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github/
|
||||||
|
.gitlab-ci.yml
|
||||||
|
|
||||||
|
# Session and runtime data (mounted as volumes)
|
||||||
|
sessions/
|
||||||
|
reports/
|
||||||
|
browser/
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
*.log
|
||||||
|
.eslintcache
|
||||||
|
setup/
|
||||||
|
|
||||||
|
# Docker files (no recursion)
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
compose.yaml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# NixOS specific files (not needed in Docker)
|
||||||
|
flake.nix
|
||||||
|
flake.lock
|
||||||
|
run.sh
|
||||||
|
|
||||||
|
# Asset files (not needed for runtime)
|
||||||
|
assets/
|
||||||
|
public/
|
||||||
28
Dockerfile
28
Dockerfile
@@ -3,7 +3,7 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
FROM node:22-slim AS builder
|
FROM node:22-slim AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
ENV PLAYWRIGHT_BROWSERS_PATH=0
|
ENV PLAYWRIGHT_BROWSERS_PATH=0
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ RUN npx playwright install --with-deps --only-shell chromium \
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
FROM node:22-slim AS runtime
|
FROM node:22-slim AS runtime
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
# Set production environment variables
|
# Set production environment variables
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
@@ -41,6 +41,9 @@ ENV NODE_ENV=production \
|
|||||||
|
|
||||||
# Install minimal system libraries required for Chromium headless to run
|
# Install minimal system libraries required for Chromium headless to run
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
cron \
|
||||||
|
gettext-base \
|
||||||
|
tzdata \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
libglib2.0-0 \
|
libglib2.0-0 \
|
||||||
libdbus-1-3 \
|
libdbus-1-3 \
|
||||||
@@ -72,16 +75,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
&& rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||||
|
|
||||||
# Copy compiled application and dependencies from builder stage
|
# Copy compiled application and dependencies from builder stage
|
||||||
COPY --from=builder /app/dist ./dist
|
COPY --from=builder /usr/src/microsoft-rewards-script/dist ./dist
|
||||||
COPY --from=builder /app/package*.json ./
|
COPY --from=builder /usr/src/microsoft-rewards-script/package*.json ./
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
COPY --from=builder /usr/src/microsoft-rewards-script/node_modules ./node_modules
|
||||||
|
|
||||||
# Copy entrypoint script
|
# Copy runtime scripts with proper permissions from the start
|
||||||
COPY docker-entrypoint.sh /usr/local/bin/
|
COPY --chmod=755 src/run_daily.sh ./src/run_daily.sh
|
||||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
COPY --chmod=644 src/crontab.template /etc/cron.d/microsoft-rewards-cron.template
|
||||||
|
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
# Use entrypoint that supports single-run and optional cron mode
|
# Entrypoint handles TZ, initial run toggle, cron templating & launch
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
CMD ["sh", "-c", "echo 'Container started; cron is running.'"]
|
||||||
# Default: single execution
|
|
||||||
CMD ["node", "--enable-source-maps", "./dist/index.js"]
|
|
||||||
19
README.md
19
README.md
@@ -180,18 +180,29 @@ The bot will automatically configure cron (Linux/Raspberry Pi) or Task Scheduler
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Docker Quick Start
|
## 🐳 Docker Quick Start
|
||||||
|
|
||||||
For containerized deployment:
|
For containerized deployment with built-in scheduling:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure accounts.jsonc exists in src/
|
# Ensure accounts.jsonc and config.jsonc exist in src/
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# View logs
|
# View logs
|
||||||
docker logs -f microsoft-rewards-bot
|
docker logs -f microsoft-rewards-script
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
docker compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Container includes:
|
||||||
|
- ✅ Built-in cron scheduling
|
||||||
|
- ✅ Automatic timezone handling
|
||||||
|
- ✅ Random execution delays (anti-detection)
|
||||||
|
- ✅ Health checks
|
||||||
|
|
||||||
|
**⚠️ Note:** Buy Mode is not available in Docker (requires interactive terminal)
|
||||||
|
|
||||||
**📖 [Full Docker Guide](docs/docker.md)**
|
**📖 [Full Docker Guide](docs/docker.md)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
48
compose.yaml
48
compose.yaml
@@ -1,29 +1,43 @@
|
|||||||
services:
|
services:
|
||||||
microsoft-rewards-bot:
|
microsoft-rewards-script:
|
||||||
build: .
|
build: .
|
||||||
container_name: microsoft-rewards-bot
|
container_name: microsoft-rewards-script
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# Volume mounts: Specify a location where you want to save the files on your local machine.
|
# Volume mounts: Specify a location where you want to save the files on your local machine.
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/accounts.jsonc:/app/src/accounts.jsonc:ro
|
- ./src/accounts.jsonc:/usr/src/microsoft-rewards-script/dist/accounts.jsonc:ro
|
||||||
- ./src/config.jsonc:/app/src/config.jsonc:ro
|
- ./src/config.jsonc:/usr/src/microsoft-rewards-script/dist/config.jsonc:ro
|
||||||
- ./sessions:/app/sessions
|
- ./sessions:/usr/src/microsoft-rewards-script/sessions # Optional, saves your login session
|
||||||
|
- ./reports:/usr/src/microsoft-rewards-script/reports # Optional, saves run reports
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
TZ: "America/Toronto" # Set your timezone for logging (and cron if enabled)
|
TZ: "America/Toronto" # Set your timezone for proper scheduling
|
||||||
NODE_ENV: "production"
|
NODE_ENV: "production"
|
||||||
# Force headless when running in Docker (uses Chromium Headless Shell only)
|
CRON_SCHEDULE: "0 7,16,20 * * *" # Customize your schedule, use crontab.guru for formatting
|
||||||
FORCE_HEADLESS: "1"
|
RUN_ON_START: "true" # Runs the script immediately on container startup
|
||||||
|
|
||||||
# Optional: enable in-container cron scheduling
|
|
||||||
#USE_CRON: "true"
|
|
||||||
#CRON_SCHEDULE: "0 9 * * *" # Daily at 9 AM (see https://crontab.guru)
|
|
||||||
#RUN_ON_START: "true" # Run once immediately on container start
|
|
||||||
|
|
||||||
|
# Add scheduled start-time randomization (uncomment to customize or disable, default: enabled)
|
||||||
|
#MIN_SLEEP_MINUTES: "5"
|
||||||
|
#MAX_SLEEP_MINUTES: "50"
|
||||||
|
SKIP_RANDOM_SLEEP: "false"
|
||||||
|
|
||||||
|
# Optionally set how long to wait before killing a stuck script run (prevents blocking future runs, default: 8 hours)
|
||||||
|
#STUCK_PROCESS_TIMEOUT_HOURS: "8"
|
||||||
|
|
||||||
|
# Optional resource limits for the container
|
||||||
|
mem_limit: 4g
|
||||||
|
cpus: 2
|
||||||
|
|
||||||
|
# Health check - monitors if cron daemon is running to ensure scheduled jobs can execute
|
||||||
|
# Container marked unhealthy if cron process dies
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "sh", "-c", "pgrep cron > /dev/null || exit 1"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
# Security hardening
|
# Security hardening
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
||||||
# Default: single run per container start
|
|
||||||
command: ["node", "--enable-source-maps", "./dist/index.js"]
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Docker entrypoint with optional cron support
|
|
||||||
# Usage:
|
|
||||||
# Default: node --enable-source-maps ./dist/index.js
|
|
||||||
# Cron mode: set USE_CRON=true
|
|
||||||
|
|
||||||
# If USE_CRON is set, configure cron for repeated runs
|
|
||||||
if [ "$USE_CRON" = "true" ] || [ "$USE_CRON" = "1" ]; then
|
|
||||||
echo "==> Cron mode enabled"
|
|
||||||
|
|
||||||
# Default cron schedule if not provided (daily at 9 AM with random jitter)
|
|
||||||
CRON_SCHEDULE="${CRON_SCHEDULE:-0 9 * * *}"
|
|
||||||
|
|
||||||
echo "==> Installing cron..."
|
|
||||||
apt-get update -qq && apt-get install -y -qq cron > /dev/null 2>&1
|
|
||||||
|
|
||||||
# Create cron job file
|
|
||||||
echo "==> Setting up cron schedule: $CRON_SCHEDULE"
|
|
||||||
|
|
||||||
# Build environment variables for cron
|
|
||||||
ENV_VARS=$(printenv | grep -E '^(TZ|NODE_ENV|FORCE_HEADLESS|PLAYWRIGHT_BROWSERS_PATH|ACCOUNTS_JSON|ACCOUNTS_FILE)=' | sed 's/^/export /' | tr '\n' ';')
|
|
||||||
|
|
||||||
# Create cron job that runs the script
|
|
||||||
CRON_JOB="$CRON_SCHEDULE cd /app && $ENV_VARS node --enable-source-maps ./dist/index.js >> /var/log/cron.log 2>&1"
|
|
||||||
|
|
||||||
echo "$CRON_JOB" > /etc/cron.d/microsoft-rewards
|
|
||||||
chmod 0644 /etc/cron.d/microsoft-rewards
|
|
||||||
|
|
||||||
# Apply cron job
|
|
||||||
crontab /etc/cron.d/microsoft-rewards
|
|
||||||
|
|
||||||
# Create log file
|
|
||||||
touch /var/log/cron.log
|
|
||||||
|
|
||||||
echo "==> Cron job installed:"
|
|
||||||
echo " Schedule: $CRON_SCHEDULE"
|
|
||||||
echo " Command: node --enable-source-maps ./dist/index.js"
|
|
||||||
echo " Logs: /var/log/cron.log"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run once immediately if requested
|
|
||||||
if [ "$RUN_ON_START" = "true" ] || [ "$RUN_ON_START" = "1" ]; then
|
|
||||||
echo "==> Running initial execution (RUN_ON_START=true)..."
|
|
||||||
cd /app
|
|
||||||
node --enable-source-maps ./dist/index.js 2>&1 | tee -a /var/log/cron.log
|
|
||||||
echo "==> Initial execution completed"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> Starting cron daemon..."
|
|
||||||
echo "==> Container ready. Cron will execute: $CRON_SCHEDULE"
|
|
||||||
echo "==> View logs: docker logs -f <container>"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Start cron in foreground and tail logs
|
|
||||||
cron && tail -f /var/log/cron.log
|
|
||||||
else
|
|
||||||
echo "==> Running single execution"
|
|
||||||
echo "==> To run on a schedule inside the container, set USE_CRON=true"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Execute passed command (default: node --enable-source-maps ./dist/index.js)
|
|
||||||
exec "$@"
|
|
||||||
fi
|
|
||||||
284
docs/docker.md
284
docs/docker.md
@@ -1,6 +1,6 @@
|
|||||||
# 🐳 Docker Guide
|
# 🐳 Docker Guide
|
||||||
|
|
||||||
Run the bot in a containerized environment with optional in-container cron support.
|
Run the bot in a containerized environment with built-in cron scheduling.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,26 +8,30 @@ Run the bot in a containerized environment with optional in-container cron suppo
|
|||||||
|
|
||||||
1. **Create required files**
|
1. **Create required files**
|
||||||
- `src/accounts.jsonc` with your credentials
|
- `src/accounts.jsonc` with your credentials
|
||||||
- `src/config.jsonc` (defaults apply if missing)
|
- `src/config.jsonc` (optional, defaults apply if missing)
|
||||||
|
|
||||||
2. **Start the container**
|
2. **Start the container**
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Watch logs**
|
3. **Watch logs**
|
||||||
```bash
|
```bash
|
||||||
docker logs -f microsoft-rewards-bot
|
docker logs -f microsoft-rewards-script
|
||||||
```
|
```
|
||||||
|
|
||||||
The container performs a single pass. Use cron, Task Scheduler, or another orchestrator to restart it on your desired cadence.
|
The container runs with cron scheduling enabled by default. Configure schedule via environment variables.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 What's Included
|
## 🎯 What's Included
|
||||||
|
|
||||||
- ✅ Chromium Headless Shell (lightweight browser runtime)
|
- ✅ **Chromium Headless Shell** — Lightweight browser runtime
|
||||||
- ✅ Cron-ready entrypoint (`docker-entrypoint.sh`)
|
- ✅ **Built-in Cron** — Automated scheduling inside container
|
||||||
- ✅ Volume mounts for persistent sessions and configs
|
- ✅ **Volume Mounts** — Persistent sessions and configs
|
||||||
- ✅ Forced headless mode for container stability
|
- ✅ **Forced Headless Mode** — Optimized for container stability
|
||||||
|
- ✅ **Health Checks** — Monitors cron daemon status
|
||||||
|
- ✅ **Random Sleep** — Spreads execution to avoid patterns
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,9 +39,10 @@ The container performs a single pass. Use cron, Task Scheduler, or another orche
|
|||||||
|
|
||||||
| Host Path | Container Path | Purpose |
|
| Host Path | Container Path | Purpose |
|
||||||
|-----------|----------------|---------|
|
|-----------|----------------|---------|
|
||||||
| `./src/accounts.jsonc` | `/app/src/accounts.jsonc` | Account credentials (read-only) |
|
| `./src/accounts.jsonc` | `/usr/src/microsoft-rewards-script/dist/accounts.jsonc` | Account credentials (read-only) |
|
||||||
| `./src/config.jsonc` | `/app/src/config.jsonc` | Configuration (read-only) |
|
| `./src/config.jsonc` | `/usr/src/microsoft-rewards-script/dist/config.jsonc` | Configuration (read-only) |
|
||||||
| `./sessions` | `/app/sessions` | Cookies, fingerprints, and job-state |
|
| `./sessions` | `/usr/src/microsoft-rewards-script/sessions` | Cookies, fingerprints, and job-state |
|
||||||
|
| `./reports` | `/usr/src/microsoft-rewards-script/reports` | Run summaries and metrics |
|
||||||
|
|
||||||
Edit `compose.yaml` to adjust paths or add additional mounts.
|
Edit `compose.yaml` to adjust paths or add additional mounts.
|
||||||
|
|
||||||
@@ -45,20 +50,54 @@ Edit `compose.yaml` to adjust paths or add additional mounts.
|
|||||||
|
|
||||||
## 🌍 Environment Variables
|
## 🌍 Environment Variables
|
||||||
|
|
||||||
|
Configure via `compose.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
microsoft-rewards-bot:
|
microsoft-rewards-script:
|
||||||
environment:
|
environment:
|
||||||
TZ: "Europe/Paris" # Container timezone (cron + logging)
|
# Required
|
||||||
NODE_ENV: "production"
|
TZ: "America/Toronto" # Container timezone
|
||||||
FORCE_HEADLESS: "1" # Required for Chromium in Docker
|
CRON_SCHEDULE: "0 7,16,20 * * *" # When to run (crontab format)
|
||||||
#USE_CRON: "true" # Optional cron mode (see below)
|
|
||||||
#CRON_SCHEDULE: "0 9 * * *"
|
# Optional
|
||||||
#RUN_ON_START: "true"
|
RUN_ON_START: "true" # Run immediately on startup
|
||||||
|
NODE_ENV: "production" # Node environment
|
||||||
|
|
||||||
|
# Randomization (spreads execution time)
|
||||||
|
MIN_SLEEP_MINUTES: "5" # Min random delay (default: 5)
|
||||||
|
MAX_SLEEP_MINUTES: "50" # Max random delay (default: 50)
|
||||||
|
SKIP_RANDOM_SLEEP: "false" # Set to "true" to disable
|
||||||
|
|
||||||
|
# Safety
|
||||||
|
STUCK_PROCESS_TIMEOUT_HOURS: "8" # Kill stuck runs (default: 8h)
|
||||||
```
|
```
|
||||||
|
|
||||||
- `ACCOUNTS_JSON` and `ACCOUNTS_FILE` can override account sources.
|
### Key Variables
|
||||||
- `ACCOUNTS_JSON` expects inline JSON; `ACCOUNTS_FILE` points to a mounted path.
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `TZ` | `UTC` | Container timezone for logs and scheduling |
|
||||||
|
| `CRON_SCHEDULE` | Required | Cron expression (use [crontab.guru](https://crontab.guru)) |
|
||||||
|
| `RUN_ON_START` | `false` | Run once immediately when container starts |
|
||||||
|
| `MIN_SLEEP_MINUTES` | `5` | Minimum random delay before execution |
|
||||||
|
| `MAX_SLEEP_MINUTES` | `50` | Maximum random delay before execution |
|
||||||
|
| `SKIP_RANDOM_SLEEP` | `false` | Disable randomization (not recommended) |
|
||||||
|
| `STUCK_PROCESS_TIMEOUT_HOURS` | `8` | Timeout for stuck processes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🕐 Cron Schedule Examples
|
||||||
|
|
||||||
|
| Schedule | Description | Cron Expression |
|
||||||
|
|----------|-------------|-----------------|
|
||||||
|
| Daily at 09:00 | Single daily run | `0 9 * * *` |
|
||||||
|
| Three times daily | 07:00, 16:00, 20:00 | `0 7,16,20 * * *` |
|
||||||
|
| Every 6 hours | Four runs per day | `0 */6 * * *` |
|
||||||
|
| Weekdays at 08:00 | Monday–Friday only | `0 8 * * 1-5` |
|
||||||
|
| Twice daily | 09:00 and 21:00 | `0 9,21 * * *` |
|
||||||
|
|
||||||
|
**Validate expressions:** [crontab.guru](https://crontab.guru)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -68,187 +107,154 @@ services:
|
|||||||
# Start container
|
# Start container
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# View logs
|
# View logs (follow)
|
||||||
docker logs -f microsoft-rewards-bot
|
docker logs -f microsoft-rewards-script
|
||||||
|
|
||||||
|
# View last 100 lines
|
||||||
|
docker logs --tail 100 microsoft-rewards-script
|
||||||
|
|
||||||
# Stop container
|
# Stop container
|
||||||
docker compose down
|
docker compose down
|
||||||
|
|
||||||
# Rebuild image
|
# Rebuild image (after code changes)
|
||||||
docker compose build --no-cache
|
docker compose build --no-cache
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
# Restart container
|
# Restart container
|
||||||
docker compose restart
|
docker compose restart
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
docker compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎛️ Scheduling Options
|
|
||||||
|
|
||||||
### Use a host scheduler (recommended)
|
|
||||||
|
|
||||||
- Trigger `docker compose up --build` (or restart the container) with cron, systemd timers, Task Scheduler, Kubernetes CronJobs, etc.
|
|
||||||
- Ensure persistent volumes are mounted so repeated runs reuse state.
|
|
||||||
- See [External Scheduling](schedule.md) for host-level examples.
|
|
||||||
|
|
||||||
### Enable in-container cron (optional)
|
|
||||||
|
|
||||||
1. Set environment variables in `docker-compose.yml`:
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
microsoft-rewards-bot:
|
|
||||||
environment:
|
|
||||||
USE_CRON: "true"
|
|
||||||
CRON_SCHEDULE: "0 9,16,21 * * *" # Example: 09:00, 16:00, 21:00
|
|
||||||
RUN_ON_START: "true" # Optional one-time run at container boot
|
|
||||||
```
|
|
||||||
2. Rebuild and redeploy:
|
|
||||||
```bash
|
|
||||||
docker compose down
|
|
||||||
docker compose build --no-cache
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
3. Confirm cron is active:
|
|
||||||
```bash
|
|
||||||
docker logs -f microsoft-rewards-bot
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Cron schedule examples
|
|
||||||
|
|
||||||
| Schedule | Description | Cron expression |
|
|
||||||
|----------|-------------|-----------------|
|
|
||||||
| Daily at 09:00 | Single run | `0 9 * * *` |
|
|
||||||
| Twice daily | 09:00 & 21:00 | `0 9,21 * * *` |
|
|
||||||
| Every 6 hours | Four runs/day | `0 */6 * * *` |
|
|
||||||
| Weekdays at 08:00 | Monday–Friday | `0 8 * * 1-5` |
|
|
||||||
|
|
||||||
Validate expressions with [crontab.guru](https://crontab.guru).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Troubleshooting
|
## 🛠️ Troubleshooting
|
||||||
|
|
||||||
| Problem | Solution |
|
| Problem | Solution |
|
||||||
|---------|----------|
|
|---------|----------|
|
||||||
| **"accounts.json not found"** | Ensure `./src/accounts.jsonc` exists and is mounted read-only |
|
| **"accounts.jsonc not found"** | Ensure file exists at `./src/accounts.jsonc` |
|
||||||
| **"Browser launch failed"** | Verify `FORCE_HEADLESS=1` and Chromium dependencies installed |
|
| **"Browser launch failed"** | Verify `FORCE_HEADLESS=1` is set (automatic in Dockerfile) |
|
||||||
| **"Permission denied"** | Check file permissions (`chmod 644 accounts.jsonc config.jsonc`) |
|
| **"Permission denied"** | Fix file permissions: `chmod 644 src/*.jsonc` |
|
||||||
| **Automation not repeating** | Enable cron (`USE_CRON=true`) or use a host scheduler |
|
| **Cron not running** | Check logs for "Cron configured" message |
|
||||||
| **Cron not working** | See [Cron troubleshooting](#-cron-troubleshooting) |
|
| **Wrong timezone** | Update `TZ` in `compose.yaml` and restart |
|
||||||
|
| **No output in logs** | Wait for cron schedule or set `RUN_ON_START=true` |
|
||||||
|
|
||||||
### Debug container
|
### Debug Container
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Enter container shell
|
# Enter container shell
|
||||||
docker exec -it microsoft-rewards-bot /bin/bash
|
docker exec -it microsoft-rewards-script /bin/bash
|
||||||
|
|
||||||
# Check Node.js version
|
# Check Node.js version
|
||||||
docker exec -it microsoft-rewards-bot node --version
|
docker exec -it microsoft-rewards-script node --version
|
||||||
|
|
||||||
# Inspect mounted config
|
# Inspect mounted config
|
||||||
docker exec -it microsoft-rewards-bot cat /app/src/config.jsonc
|
docker exec -it microsoft-rewards-script cat /usr/src/microsoft-rewards-script/dist/config.jsonc
|
||||||
|
|
||||||
# Check env vars
|
# Check environment variables
|
||||||
docker exec -it microsoft-rewards-bot printenv | grep -E "TZ|USE_CRON|CRON_SCHEDULE"
|
docker exec -it microsoft-rewards-script printenv | grep -E "TZ|CRON"
|
||||||
|
|
||||||
|
# View cron configuration
|
||||||
|
docker exec -it microsoft-rewards-script crontab -l
|
||||||
|
|
||||||
|
# Check if cron is running
|
||||||
|
docker exec -it microsoft-rewards-script ps aux | grep cron
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Switching cron on or off
|
## <EFBFBD> Health Check
|
||||||
|
|
||||||
- **Enable cron:** set `USE_CRON=true`, provide `CRON_SCHEDULE`, rebuild, and redeploy.
|
The container includes a health check that monitors the cron daemon:
|
||||||
- **Disable cron:** remove `USE_CRON` (and related variables). The container will run once per start; handle recurrence externally.
|
|
||||||
|
```yaml
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "sh", "-c", "pgrep cron > /dev/null || exit 1"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
```
|
||||||
|
|
||||||
|
Check health status:
|
||||||
|
```bash
|
||||||
|
docker inspect --format='{{.State.Health.Status}}' microsoft-rewards-script
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🐛 Cron troubleshooting
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
| Problem | Solution |
|
### Buy Mode Not Supported
|
||||||
|---------|----------|
|
|
||||||
| **Cron not executing** | Check logs for "Cron mode enabled" and cron syntax errors |
|
|
||||||
| **Wrong timezone** | Ensure `TZ` matches your location |
|
|
||||||
| **Syntax error** | Validate expression at [crontab.guru](https://crontab.guru) |
|
|
||||||
| **No logs generated** | Tail `/var/log/cron.log` inside the container |
|
|
||||||
| **Duplicate runs** | Ensure only one cron entry is configured |
|
|
||||||
|
|
||||||
### Inspect cron inside the container
|
**Buy Mode cannot be used in Docker** because it requires interactive terminal input. Use Buy Mode only in local installations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it microsoft-rewards-bot /bin/bash
|
# ✅ Works locally
|
||||||
ps aux | grep cron
|
npm run buy
|
||||||
crontab -l
|
|
||||||
tail -100 /var/log/cron.log
|
# ❌ Does not work in Docker
|
||||||
|
docker exec microsoft-rewards-script npm run buy
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
For manual redemptions, run the bot locally outside Docker.
|
||||||
|
|
||||||
## 📚 Next steps
|
### Headless Mode Required
|
||||||
|
|
||||||
- [Configuration guide](config.md)
|
Docker containers **must run in headless mode**. The Dockerfile automatically sets `FORCE_HEADLESS=1`. Do not disable this.
|
||||||
- [External scheduling](schedule.md)
|
|
||||||
- [Humanization guide](humanization.md)
|
### Random Sleep Behavior
|
||||||
|
|
||||||
|
- **Enabled by default** to avoid detection patterns
|
||||||
|
- Adds 5-50 minutes random delay before each run
|
||||||
|
- Disable only for testing: `SKIP_RANDOM_SLEEP=true`
|
||||||
|
- First run (when `RUN_ON_START=true`) skips random sleep
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Option 3: Single Run (Manual)
|
## <20> Resource Limits
|
||||||
|
|
||||||
|
Recommended settings in `compose.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
rewards:
|
microsoft-rewards-script:
|
||||||
build: .
|
mem_limit: 4g # Maximum RAM
|
||||||
command: ["node", "./dist/index.js"]
|
cpus: 2 # CPU cores
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Adjust based on your system and number of accounts.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔄 Switching Cron On or Off
|
## 🔒 Security Hardening
|
||||||
|
|
||||||
- **Enable cron:** set `USE_CRON=true`, provide `CRON_SCHEDULE`, rebuild the image, and redeploy.
|
The compose file includes security measures:
|
||||||
- **Disable cron:** remove `USE_CRON` (and related variables). The container will run once per start; use host automation to relaunch when needed.
|
|
||||||
|
|
||||||
---
|
```yaml
|
||||||
|
security_opt:
|
||||||
## 🐛 Cron Troubleshooting
|
- no-new-privileges:true # Prevents privilege escalation
|
||||||
|
|
||||||
| Problem | Solution |
|
|
||||||
|---------|----------|
|
|
||||||
| **Cron not executing** | Check `docker logs` for "Cron mode enabled" message |
|
|
||||||
| **Wrong timezone** | Verify `TZ` environment variable matches your location |
|
|
||||||
| **Syntax error** | Validate cron expression at [crontab.guru](https://crontab.guru) |
|
|
||||||
| **No logs** | Use `docker exec <container> tail -f /var/log/cron.log` |
|
|
||||||
| **Multiple executions** | Check for duplicate cron entries |
|
|
||||||
|
|
||||||
### Debug Cron Inside Container
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Enter container
|
|
||||||
docker exec -it microsoft-rewards-bot /bin/bash
|
|
||||||
|
|
||||||
# Check cron is running
|
|
||||||
ps aux | grep cron
|
|
||||||
|
|
||||||
# View installed cron jobs
|
|
||||||
crontab -l
|
|
||||||
|
|
||||||
# Check cron logs
|
|
||||||
tail -100 /var/log/cron.log
|
|
||||||
|
|
||||||
# Test environment variables
|
|
||||||
printenv | grep -E 'TZ|NODE_ENV'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Volumes are mounted **read-only** (`:ro`) for credentials to prevent tampering.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 Next Steps
|
## 📚 Next Steps
|
||||||
|
|
||||||
**Need 2FA?**
|
**Need 2FA setup?**
|
||||||
→ **[Accounts & TOTP Setup](./accounts.md)**
|
→ **[Accounts & TOTP Guide](./accounts.md)**
|
||||||
|
|
||||||
**Want notifications?**
|
**Want notifications?**
|
||||||
→ **[Discord Webhooks](./conclusionwebhook.md)**
|
→ **[Discord Webhooks](./conclusionwebhook.md)**
|
||||||
|
→ **[NTFY Push Alerts](./ntfy.md)**
|
||||||
|
|
||||||
**Need scheduling tips?**
|
**Need proxy configuration?**
|
||||||
→ **[External Scheduling](./schedule.md)**
|
→ **[Proxy Setup](./proxy.md)**
|
||||||
|
|
||||||
|
**External scheduling?**
|
||||||
|
→ **[Scheduling Guide](./schedule.md)**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
50
entrypoint.sh
Normal file
50
entrypoint.sh
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Ensure Playwright uses preinstalled browsers
|
||||||
|
export PLAYWRIGHT_BROWSERS_PATH=0
|
||||||
|
|
||||||
|
# 1. Timezone: default to UTC if not provided
|
||||||
|
: "${TZ:=UTC}"
|
||||||
|
ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime
|
||||||
|
echo "$TZ" > /etc/timezone
|
||||||
|
dpkg-reconfigure -f noninteractive tzdata
|
||||||
|
|
||||||
|
# 2. Validate CRON_SCHEDULE
|
||||||
|
if [ -z "${CRON_SCHEDULE:-}" ]; then
|
||||||
|
echo "ERROR: CRON_SCHEDULE environment variable is not set." >&2
|
||||||
|
echo "Please set CRON_SCHEDULE (e.g., \"0 2 * * *\")." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Initial run without sleep if RUN_ON_START=true
|
||||||
|
if [ "${RUN_ON_START:-false}" = "true" ]; then
|
||||||
|
echo "[entrypoint] Starting initial run in background at $(date)"
|
||||||
|
(
|
||||||
|
cd /usr/src/microsoft-rewards-script || {
|
||||||
|
echo "[entrypoint-bg] ERROR: Unable to cd to /usr/src/microsoft-rewards-script" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
# Skip random sleep for initial run, but preserve setting for cron jobs
|
||||||
|
SKIP_RANDOM_SLEEP=true src/run_daily.sh
|
||||||
|
echo "[entrypoint-bg] Initial run completed at $(date)"
|
||||||
|
) &
|
||||||
|
echo "[entrypoint] Background process started (PID: $!)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Template and register cron file with explicit timezone export
|
||||||
|
if [ ! -f /etc/cron.d/microsoft-rewards-cron.template ]; then
|
||||||
|
echo "ERROR: Cron template /etc/cron.d/microsoft-rewards-cron.template not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Export TZ for envsubst to use
|
||||||
|
export TZ
|
||||||
|
envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron
|
||||||
|
chmod 0644 /etc/cron.d/microsoft-rewards-cron
|
||||||
|
crontab /etc/cron.d/microsoft-rewards-cron
|
||||||
|
|
||||||
|
echo "[entrypoint] Cron configured with schedule: $CRON_SCHEDULE and timezone: $TZ; starting cron at $(date)"
|
||||||
|
|
||||||
|
# 5. Start cron in foreground (PID 1)
|
||||||
|
exec cron -f
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
// Search
|
// Search
|
||||||
"search": {
|
"search": {
|
||||||
"useLocalQueries": true,
|
"useLocalQueries": false,
|
||||||
"settings": {
|
"settings": {
|
||||||
"useGeoLocaleQueries": true,
|
"useGeoLocaleQueries": true,
|
||||||
"scrollRandomResults": true,
|
"scrollRandomResults": true,
|
||||||
|
|||||||
2
src/crontab.template
Normal file
2
src/crontab.template
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Run automation according to CRON_SCHEDULE; redirect both stdout & stderr to Docker logs
|
||||||
|
${CRON_SCHEDULE} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /proc/1/fd/1 2>&1
|
||||||
156
src/run_daily.sh
Normal file
156
src/run_daily.sh
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export PATH="/usr/local/bin:/usr/bin:/bin"
|
||||||
|
export PLAYWRIGHT_BROWSERS_PATH=0
|
||||||
|
export TZ="${TZ:-UTC}"
|
||||||
|
|
||||||
|
cd /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
|
LOCKFILE=/tmp/run_daily.lock
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Function: Check and fix lockfile integrity
|
||||||
|
# -------------------------------
|
||||||
|
self_heal_lockfile() {
|
||||||
|
# If lockfile exists but is empty → remove it
|
||||||
|
if [ -f "$LOCKFILE" ]; then
|
||||||
|
local lock_content
|
||||||
|
lock_content=$(<"$LOCKFILE" || echo "")
|
||||||
|
|
||||||
|
if [[ -z "$lock_content" ]]; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Found empty lockfile → removing."
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If lockfile contains non-numeric PID → remove it
|
||||||
|
if ! [[ "$lock_content" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Found corrupted lockfile content ('$lock_content') → removing."
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If lockfile contains PID but process is dead → remove it
|
||||||
|
if ! kill -0 "$lock_content" 2>/dev/null; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Lockfile PID $lock_content is dead → removing stale lock."
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Function: Acquire lock
|
||||||
|
# -------------------------------
|
||||||
|
acquire_lock() {
|
||||||
|
local max_attempts=5
|
||||||
|
local attempt=0
|
||||||
|
local timeout_hours=${STUCK_PROCESS_TIMEOUT_HOURS:-8}
|
||||||
|
local timeout_seconds=$((timeout_hours * 3600))
|
||||||
|
|
||||||
|
while [ $attempt -lt $max_attempts ]; do
|
||||||
|
# Try to create lock with current PID
|
||||||
|
if (set -C; echo "$$" > "$LOCKFILE") 2>/dev/null; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Lock acquired successfully (PID: $$)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Lock exists, validate it
|
||||||
|
if [ -f "$LOCKFILE" ]; then
|
||||||
|
local existing_pid
|
||||||
|
existing_pid=$(<"$LOCKFILE" || echo "")
|
||||||
|
|
||||||
|
echo "[$(date)] [run_daily.sh] Lock file exists with PID: '$existing_pid'"
|
||||||
|
|
||||||
|
# If lockfile content is invalid → delete and retry
|
||||||
|
if [[ -z "$existing_pid" || ! "$existing_pid" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Removing invalid lockfile → retrying..."
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If process is dead → delete and retry
|
||||||
|
if ! kill -0 "$existing_pid" 2>/dev/null; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Removing stale lock (dead PID: $existing_pid)"
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check process runtime → kill if exceeded timeout
|
||||||
|
local process_age
|
||||||
|
if process_age=$(ps -o etimes= -p "$existing_pid" 2>/dev/null | tr -d ' '); then
|
||||||
|
if [ "$process_age" -gt "$timeout_seconds" ]; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Killing stuck process $existing_pid (${process_age}s > ${timeout_hours}h)"
|
||||||
|
kill -TERM "$existing_pid" 2>/dev/null || true
|
||||||
|
sleep 5
|
||||||
|
kill -KILL "$existing_pid" 2>/dev/null || true
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[$(date)] [run_daily.sh] Lock held by PID $existing_pid, attempt $((attempt + 1))/$max_attempts"
|
||||||
|
sleep 2
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "[$(date)] [run_daily.sh] Could not acquire lock after $max_attempts attempts; exiting."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Function: Release lock
|
||||||
|
# -------------------------------
|
||||||
|
release_lock() {
|
||||||
|
if [ -f "$LOCKFILE" ]; then
|
||||||
|
local lock_pid
|
||||||
|
lock_pid=$(<"$LOCKFILE")
|
||||||
|
if [ "$lock_pid" = "$$" ]; then
|
||||||
|
rm -f "$LOCKFILE"
|
||||||
|
echo "[$(date)] [run_daily.sh] Lock released (PID: $$)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Always release lock on exit — but only if we acquired it
|
||||||
|
trap 'release_lock' EXIT INT TERM
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# MAIN EXECUTION FLOW
|
||||||
|
# -------------------------------
|
||||||
|
echo "[$(date)] [run_daily.sh] Current process PID: $$"
|
||||||
|
|
||||||
|
# Self-heal any broken or empty locks before proceeding
|
||||||
|
self_heal_lockfile
|
||||||
|
|
||||||
|
# Attempt to acquire the lock safely
|
||||||
|
if ! acquire_lock; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Random sleep between MIN and MAX to spread execution
|
||||||
|
MINWAIT=${MIN_SLEEP_MINUTES:-5}
|
||||||
|
MAXWAIT=${MAX_SLEEP_MINUTES:-50}
|
||||||
|
MINWAIT_SEC=$((MINWAIT*60))
|
||||||
|
MAXWAIT_SEC=$((MAXWAIT*60))
|
||||||
|
|
||||||
|
if [ "${SKIP_RANDOM_SLEEP:-false}" != "true" ]; then
|
||||||
|
SLEEPTIME=$(( MINWAIT_SEC + RANDOM % (MAXWAIT_SEC - MINWAIT_SEC) ))
|
||||||
|
echo "[$(date)] [run_daily.sh] Sleeping for $((SLEEPTIME/60)) minutes ($SLEEPTIME seconds)"
|
||||||
|
sleep "$SLEEPTIME"
|
||||||
|
else
|
||||||
|
echo "[$(date)] [run_daily.sh] Skipping random sleep"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start the actual script
|
||||||
|
echo "[$(date)] [run_daily.sh] Starting script..."
|
||||||
|
if npm start; then
|
||||||
|
echo "[$(date)] [run_daily.sh] Script completed successfully."
|
||||||
|
else
|
||||||
|
echo "[$(date)] [run_daily.sh] ERROR: Script failed!" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[$(date)] [run_daily.sh] Script finished"
|
||||||
|
# Lock is released automatically via trap
|
||||||
Reference in New Issue
Block a user