From 160e792e8fa9b83c7abe1657d93da11fa6414ac3 Mon Sep 17 00:00:00 2001 From: Wiktor <42137698+overspend1@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:45:49 +0100 Subject: [PATCH] fix: support compose env overrides --- .dockerignore | 38 +++++++----------------------------- .gitignore | 2 +- DOCKER_DEPLOY.md | 14 ++++++++++---- Dockerfile | 32 ++++++++++++++++++------------- README.md | 8 +++++--- bot.py | 48 ++++++++++++++++++++++++++++------------------ compose.env | 9 +++++++++ docker-compose.yml | 34 +++++++++++++++++++++++--------- 8 files changed, 105 insertions(+), 80 deletions(-) create mode 100644 compose.env diff --git a/.dockerignore b/.dockerignore index 6bc9356..3e545aa 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,32 +1,8 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -.venv/ -venv/ -ENV/ - -# Database -*.db -*.db-journal - -# Environment variables +.git +.gitignore +__pycache__ +*.pyc +backups .env - -# IDEs -.vscode/ -.idea/ -*.swp -*.swo - -# OS -.DS_Store -Thumbs.db - -# Logs -*.log - -# Docker -data/ +.env.* +.ENV diff --git a/.gitignore b/.gitignore index e184a5a..b7bf04f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ __pycache__/ *.pyc memes.db -.env \ No newline at end of file +.env diff --git a/DOCKER_DEPLOY.md b/DOCKER_DEPLOY.md index 9c14b30..948fb52 100644 --- a/DOCKER_DEPLOY.md +++ b/DOCKER_DEPLOY.md @@ -37,8 +37,8 @@ sudo usermod -aG docker $USER Copy the example file and edit it: ```bash -cp .env.example .env -nano .env # or use any text editor +cp .env.example .ENV +nano .ENV # or use any text editor ``` Fill in your actual values: @@ -49,6 +49,8 @@ CHANNEL_ID=@yourchannel POSTGRES_DB=meme_wrangler POSTGRES_USER=meme POSTGRES_PASSWORD=meme +# Optional: adjust which env file Compose should read (defaults to compose.env) +# COMPOSE_ENV_FILE=.ENV # Optional: hash (SHA-256) for replacing the baked-in backup secret # MEMEBOT_BACKUP_PASSWORD_HASH= # Optional: adjust DATABASE_URL for non-compose workflows @@ -58,8 +60,8 @@ POSTGRES_PASSWORD=meme ### 2. Build and Run with Docker Compose (Easiest) ```bash -# Build and start in background -docker-compose up -d +# Build and start in background (use COMPOSE_ENV_FILE to load your secrets file) +COMPOSE_ENV_FILE=.ENV docker-compose up -d # View logs docker-compose logs -f @@ -73,6 +75,10 @@ docker-compose restart That's it! Your bot is now running in Docker! 🎉 +#### Deploying with Portainer Stacks + +When launching the stack from Portainer, upload your `.ENV` file through the **Environment variables** tab and add `COMPOSE_ENV_FILE=.ENV` so the compose file loads it. Portainer stores the uploaded file under `/data/compose//.ENV`, matching the value provided through `COMPOSE_ENV_FILE`. + ## Alternative: Using Docker Commands Directly ### Build the Image diff --git a/Dockerfile b/Dockerfile index 6ddea87..0c59710 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,38 @@ # Use Python 3.12 slim image FROM python:3.12-slim +# Configure Python environment +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + # Set working directory WORKDIR /app -# Install system dependencies (if needed) -RUN apt-get update && apt-get install -y \ +# Install system dependencies required for asyncpg build +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev \ && rm -rf /var/lib/apt/lists/* # Copy requirements first (for better caching) -COPY requirements.txt . +COPY requirements.txt ./ # Install Python dependencies -RUN pip install --no-cache-dir -r requirements.txt -RUN pip install --no-cache-dir python-telegram-bot==20.7 +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements.txt -# Copy the bot code -COPY bot.py . +# Copy application source +COPY . . -# Create directory for backups +# Ensure backups directory exists inside the container RUN mkdir -p /app/backups # Set environment variables (will be overridden by docker-compose or run command) -ENV TELEGRAM_BOT_TOKEN="" -ENV OWNER_ID="" -ENV CHANNEL_ID="" -ENV DATABASE_URL="" -ENV MEMEBOT_BACKUP_DIR="/app/backups" +ENV TELEGRAM_BOT_TOKEN="" \ + OWNER_ID="" \ + CHANNEL_ID="" \ + DATABASE_URL="" \ + MEMEBOT_BACKUP_DIR="/app/backups" # Run the bot CMD ["python", "bot.py"] diff --git a/README.md b/README.md index 84492e7..0d7c3e5 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ The easiest way to run the bot is using Docker: 1. **Set up environment variables:** ```bash - cp .env.example .env - nano .env # Edit with your bot credentials + cp .env.example .ENV + nano .ENV # Edit with your bot credentials ``` - The compose stack expects PostgreSQL credentials, so populate `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` (defaults provided). Backups are protected by a built-in SHA-256 hash; optionally define `MEMEBOT_BACKUP_PASSWORD_HASH` to replace it. + By default the compose file reads values from `compose.env` so it can boot without secrets. To keep credentials out of version control, point the stack at your own file by exporting `COMPOSE_ENV_FILE` before running Compose (e.g. `COMPOSE_ENV_FILE=.ENV docker compose up -d`). Populate `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` (defaults provided). Backups are protected by a built-in SHA-256 hash; optionally define `MEMEBOT_BACKUP_PASSWORD_HASH` to replace it. 2. **Run with Docker Compose:** ```bash @@ -25,6 +25,8 @@ The easiest way to run the bot is using Docker: docker-compose logs -f ``` + Deploying via Portainer? Upload your `.ENV` file under **Environment variables** and set `COMPOSE_ENV_FILE=.ENV` so the stack loads it automatically. + 4. **Stop the bot:** ```bash docker-compose down diff --git a/bot.py b/bot.py index 487a5cc..2979afd 100644 --- a/bot.py +++ b/bot.py @@ -30,31 +30,41 @@ if TYPE_CHECKING: from telegram import Update, Message, InputFile from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters else: - Update = Message = InputFile = Any # type: ignore - ContextTypes = SimpleNamespace(DEFAULT_TYPE=Any) # type: ignore + try: + from telegram import Update, Message, InputFile # type: ignore + from telegram.ext import ( # type: ignore + ApplicationBuilder, + ContextTypes, + CommandHandler, + MessageHandler, + filters, + ) + except ModuleNotFoundError: + Update = Message = InputFile = Any # type: ignore + ContextTypes = SimpleNamespace(DEFAULT_TYPE=Any) # type: ignore - class _MissingTelegramModule: - """Lazily raise when Telegram features are used without dependency.""" + class _MissingTelegramModule: + """Lazily raise when Telegram features are used without dependency.""" - def __getattr__(self, item): - raise RuntimeError( - "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram module)." - ) + def __getattr__(self, item): + raise RuntimeError( + "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram module)." + ) - def __call__(self, *args, **kwargs): - raise RuntimeError( - "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram module)." - ) + def __call__(self, *args, **kwargs): + raise RuntimeError( + "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram module)." + ) - ApplicationBuilder = CommandHandler = MessageHandler = _MissingTelegramModule() # type: ignore + ApplicationBuilder = CommandHandler = MessageHandler = _MissingTelegramModule() # type: ignore - class _MissingFilters(SimpleNamespace): - def __getattr__(self, item): - raise RuntimeError( - "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram filters)." - ) + class _MissingFilters(SimpleNamespace): + def __getattr__(self, item): + raise RuntimeError( + "python-telegram-bot must be installed to use the Meme Wrangler bot (missing telegram filters)." + ) - filters = _MissingFilters() # type: ignore + filters = _MissingFilters() # type: ignore logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/compose.env b/compose.env new file mode 100644 index 0000000..79dd7e6 --- /dev/null +++ b/compose.env @@ -0,0 +1,9 @@ +# Default Compose environment values. Override by setting COMPOSE_ENV_FILE to your own file. +TELEGRAM_BOT_TOKEN= +OWNER_ID=0 +CHANNEL_ID= +POSTGRES_DB=meme_wrangler +POSTGRES_USER=meme +POSTGRES_PASSWORD=meme +MEMEBOT_BACKUP_DIR=/app/backups +# MEMEBOT_BACKUP_PASSWORD_HASH= diff --git a/docker-compose.yml b/docker-compose.yml index 6c7c5df..752b657 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,43 @@ +x-env-file: &compose_env_file ${COMPOSE_ENV_FILE:-compose.env} + services: postgres: image: postgres:15 container_name: meme-wrangler-db restart: unless-stopped + env_file: + - *compose_env_file environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB:-meme_wrangler} + - POSTGRES_USER=${POSTGRES_USER:-meme} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-meme} volumes: - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-meme} -d ${POSTGRES_DB:-meme_wrangler}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s meme-wrangler: build: . container_name: meme-wrangler restart: unless-stopped depends_on: - - postgres + postgres: + condition: service_healthy + env_file: + - *compose_env_file environment: - - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} - - OWNER_ID=${OWNER_ID} - - CHANNEL_ID=${CHANNEL_ID} - - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} - - MEMEBOT_BACKUP_DIR=/app/backups + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} + - OWNER_ID=${OWNER_ID:-} + - CHANNEL_ID=${CHANNEL_ID:-} + - POSTGRES_DB=${POSTGRES_DB:-meme_wrangler} + - POSTGRES_USER=${POSTGRES_USER:-meme} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-meme} + - DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-meme}:${POSTGRES_PASSWORD:-meme}@postgres:5432/${POSTGRES_DB:-meme_wrangler}} + - MEMEBOT_BACKUP_DIR=${MEMEBOT_BACKUP_DIR:-/app/backups} volumes: - ./backups:/app/backups logging: