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 1/2] 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: From 66c5a5a7970e90206097b536f848419a44615e21 Mon Sep 17 00:00:00 2001 From: Wiktor <42137698+overspend1@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:55:27 +0100 Subject: [PATCH 2/2] fix: default compose stack to .ENV --- compose.env => .ENV.example | 3 ++- .gitignore | 1 + AGENTS.md | 2 +- DOCKER_DEPLOY.md | 22 +++++++++++----------- README.md | 6 +++--- deploy.sh | 22 +++++++++++++--------- docker-compose.yml | 2 +- 7 files changed, 32 insertions(+), 26 deletions(-) rename compose.env => .ENV.example (62%) diff --git a/compose.env b/.ENV.example similarity index 62% rename from compose.env rename to .ENV.example index 79dd7e6..f768c76 100644 --- a/compose.env +++ b/.ENV.example @@ -1,4 +1,5 @@ -# Default Compose environment values. Override by setting COMPOSE_ENV_FILE to your own file. +# Sample environment for Meme Wrangler Docker stack. +# Copy to `.ENV` and adjust secrets before deploying. TELEGRAM_BOT_TOKEN= OWNER_ID=0 CHANNEL_ID= diff --git a/.gitignore b/.gitignore index b7bf04f..d98b4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ *.pyc memes.db .env +.ENV diff --git a/AGENTS.md b/AGENTS.md index 972f959..41232a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,6 +29,6 @@ - Link issues or TODOs, flag breaking changes in bold, and include screenshots only when UI-facing Telegram copy changes. ## Deployment & Secret Tips -- Keep `.env` out of version control; copy from `.env.example` and set `TELEGRAM_BOT_TOKEN`, `OWNER_ID`, `CHANNEL_ID`, `POSTGRES_*` credentials, and `DATABASE_URL` for local runs. +- Keep `.ENV` (or `.env`) out of version control; copy from `.ENV.example` and set `TELEGRAM_BOT_TOKEN`, `OWNER_ID`, `CHANNEL_ID`, `POSTGRES_*` credentials, and `DATABASE_URL` for local runs. - Only override `MEMEBOT_BACKUP_PASSWORD_HASH` if you plan to replace the baked-in SHA-256 hash for backup commands. - Docker workflows mount backups in `./backups/` and keep the PostgreSQL cluster in the `pgdata` volume; prune carefully when resetting schedules. New memes automatically generate a fresh backup file in that directory. diff --git a/DOCKER_DEPLOY.md b/DOCKER_DEPLOY.md index 948fb52..d5369d3 100644 --- a/DOCKER_DEPLOY.md +++ b/DOCKER_DEPLOY.md @@ -37,7 +37,7 @@ sudo usermod -aG docker $USER Copy the example file and edit it: ```bash -cp .env.example .ENV +cp .ENV.example .ENV nano .ENV # or use any text editor ``` @@ -49,8 +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: adjust which env file Compose should read (defaults to .ENV) +# COMPOSE_ENV_FILE=staging.env # Optional: hash (SHA-256) for replacing the baked-in backup secret # MEMEBOT_BACKUP_PASSWORD_HASH= # Optional: adjust DATABASE_URL for non-compose workflows @@ -60,8 +60,8 @@ POSTGRES_PASSWORD=meme ### 2. Build and Run with Docker Compose (Easiest) ```bash -# Build and start in background (use COMPOSE_ENV_FILE to load your secrets file) -COMPOSE_ENV_FILE=.ENV docker-compose up -d +# Build and start in background (stack reads .ENV automatically) +docker-compose up -d # View logs docker-compose logs -f @@ -77,7 +77,7 @@ 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`. +When launching the stack from Portainer, upload your `.ENV` file through the **Environment variables** tab—Portainer saves it beside the stack as `/data/compose//.ENV`, which matches the default expected by `docker-compose.yml`. Only set `COMPOSE_ENV_FILE` if you deliberately use a different name. ## Alternative: Using Docker Commands Directly @@ -158,9 +158,9 @@ ssh -i /path/to/ssh_key username@server_ip # Navigate to bot directory cd ~/meme-wrangler -# Create .env file -cp .env.example .env -nano .env # Fill in your credentials +# Create .ENV file +cp .ENV.example .ENV +nano .ENV # Fill in your credentials # Build and run docker-compose up -d @@ -314,8 +314,8 @@ scp -i /path/to/key -r \ ssh -i /path/to/key username@server_ip cd ~/memebot -# Create .env file -nano .env # Add your credentials +# Create .ENV file +nano .ENV # Add your credentials # Run with Docker Compose docker-compose up -d diff --git a/README.md b/README.md index 0d7c3e5..16a1b30 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 + cp .ENV.example .ENV nano .ENV # Edit with your bot credentials ``` - 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. + The compose file now looks for `.ENV` by default so Portainer and other orchestrators can supply secrets without additional flags. Copy `.ENV.example` to `.ENV`, then populate `TELEGRAM_BOT_TOKEN`, `OWNER_ID`, `CHANNEL_ID`, and the Postgres settings. You can still point the stack at a different file by exporting `COMPOSE_ENV_FILE` (e.g. `COMPOSE_ENV_FILE=staging.env docker compose up -d`). 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,7 +25,7 @@ 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. + Deploying via Portainer? Upload your `.ENV` file under **Environment variables**—Portainer stores it beside the stack so the compose file picks it up automatically. Override `COMPOSE_ENV_FILE` only when you use a differently named file. 4. **Stop the bot:** ```bash diff --git a/deploy.sh b/deploy.sh index 7dea455..02436c8 100755 --- a/deploy.sh +++ b/deploy.sh @@ -26,16 +26,20 @@ if [ -z "$SERVER" ]; then exit 1 fi -echo "Step 1: Checking if .env file exists..." -if [ ! -f ".env" ]; then - echo "Error: .env file not found!" - echo "Please create .env file with your credentials:" - echo " cp .env.example .env" - echo " nano .env" +ENV_FILE=".ENV" +echo "Step 1: Checking for environment file (.ENV or .env)..." +if [ -f "$ENV_FILE" ]; then + echo "✓ .ENV file found" +elif [ -f ".env" ]; then + ENV_FILE=".env" + echo "✓ .env file found" +else + echo "Error: No environment file found!" + echo "Please create one with your credentials:" + echo " cp .ENV.example .ENV" + echo " nano .ENV" exit 1 fi - -echo "✓ .env file found" echo "" echo "Step 2: Uploading files to server..." @@ -45,7 +49,7 @@ scp -i "$SSH_KEY" \ docker-compose.yml \ bot.py \ requirements.txt \ - .env \ + "$ENV_FILE" \ "$SERVER":~/meme-wrangler/ echo "✓ Files uploaded" diff --git a/docker-compose.yml b/docker-compose.yml index 752b657..71f40d3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -x-env-file: &compose_env_file ${COMPOSE_ENV_FILE:-compose.env} +x-env-file: &compose_env_file ${COMPOSE_ENV_FILE:-.ENV} services: postgres: