diff --git a/.ENV.example b/.ENV.example index f768c76..d66caf6 100644 --- a/.ENV.example +++ b/.ENV.example @@ -6,5 +6,7 @@ CHANNEL_ID= POSTGRES_DB=meme_wrangler POSTGRES_USER=meme POSTGRES_PASSWORD=meme +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 MEMEBOT_BACKUP_DIR=/app/backups # MEMEBOT_BACKUP_PASSWORD_HASH= diff --git a/DOCKER_DEPLOY.md b/DOCKER_DEPLOY.md index d5369d3..ddfd23c 100644 --- a/DOCKER_DEPLOY.md +++ b/DOCKER_DEPLOY.md @@ -49,6 +49,8 @@ CHANNEL_ID=@yourchannel POSTGRES_DB=meme_wrangler POSTGRES_USER=meme POSTGRES_PASSWORD=meme +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 # 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 @@ -57,6 +59,8 @@ POSTGRES_PASSWORD=meme # DATABASE_URL=postgresql://meme:meme@postgres:5432/meme_wrangler ``` +Leave `POSTGRES_HOST=postgres` when running through Docker Compose or Portainer; it's the internal service name that the bot rewrites into any localhost-style URLs so the container connects to the bundled PostgreSQL instance. Override it only if your database lives elsewhere. + ### 2. Build and Run with Docker Compose (Easiest) ```bash diff --git a/README.md b/README.md index 16a1b30..17bc598 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The easiest way to run the bot is using Docker: cp .ENV.example .ENV nano .ENV # Edit with your bot credentials ``` - 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. + 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. Leave `POSTGRES_HOST=postgres` (and `POSTGRES_PORT=5432` unless your database listens elsewhere) when you run inside Docker. The bot rewrites any localhost URLs with that host so the container connects to the bundled PostgreSQL service instead of looping back on itself. 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 @@ -53,6 +53,9 @@ export TELEGRAM_BOT_TOKEN=123:ABC export OWNER_ID=123456789 export CHANNEL_ID=@yourchannel # or -1001234567890 export DATABASE_URL=postgresql://meme:meme@localhost:5432/meme_wrangler +# Optional pieces that mirror the Docker variables +# export POSTGRES_HOST=localhost +# export POSTGRES_PORT=5432 # Optional: where JSON backups are written # export MEMEBOT_BACKUP_DIR=/path/to/backups # export MEMEBOT_BACKUP_PASSWORD_HASH= diff --git a/bot.py b/bot.py index 2979afd..6b6f54a 100644 --- a/bot.py +++ b/bot.py @@ -10,6 +10,7 @@ import io import json import logging import os +from urllib.parse import urlparse, urlunparse from datetime import datetime, time, timedelta from pathlib import Path from types import SimpleNamespace @@ -89,7 +90,63 @@ def _ensure_ist(dt: datetime) -> datetime: return _ist_localize(dt) return dt.astimezone(IST) -DATABASE_URL = os.environ.get("DATABASE_URL") or os.environ.get("MEMEBOT_DB") +def _build_database_url() -> Optional[str]: + """Derive the database URL from explicit env vars or component pieces.""" + + raw_url = os.environ.get("DATABASE_URL") or os.environ.get("MEMEBOT_DB") + if not raw_url: + user = os.environ.get("POSTGRES_USER") + password = os.environ.get("POSTGRES_PASSWORD") + db_name = os.environ.get("POSTGRES_DB") + host = os.environ.get("POSTGRES_HOST", "localhost") + port = os.environ.get("POSTGRES_PORT", "5432") + if user and password and db_name: + raw_url = ( + f"postgresql://{quote(user, safe='')}:{quote(password, safe='')}" + f"@{host}:{port}/{db_name}" + ) + if raw_url: + return _normalize_database_url(raw_url) + return None + + +def _normalize_database_url(url: str) -> str: + """Replace localhost hosts with the configured Postgres host for container runs.""" + + host_override = os.environ.get("POSTGRES_HOST") + if not host_override: + return url + + host_override = host_override.strip() + if not host_override or host_override in {"localhost", "127.0.0.1", "::1"}: + return url + + parsed = urlparse(url) + if parsed.hostname not in {"localhost", "127.0.0.1", "::1"}: + return url + + if "@" in parsed.netloc: + auth_prefix, _, _ = parsed.netloc.rpartition("@") + auth_segment = f"{auth_prefix}@" + else: + auth_segment = "" + + if parsed.port is not None: + port_fragment = f":{parsed.port}" + else: + port_env = os.environ.get("POSTGRES_PORT") + port_fragment = f":{port_env}" if port_env else "" + + target_host = host_override + if ":" in target_host and not target_host.startswith("["): + target_host = f"[{target_host}]" + + new_netloc = f"{auth_segment}{target_host}{port_fragment}" + rebuilt = parsed._replace(netloc=new_netloc) + return urlunparse(rebuilt) + + +DATABASE_URL = _build_database_url() BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") OWNER_ID = int(os.environ.get("OWNER_ID", "0")) CHANNEL_ID = os.environ.get("CHANNEL_ID") # @channelusername or -100 diff --git a/docker-compose.yml b/docker-compose.yml index 71f40d3..fd1a33b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,9 @@ services: - 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}} + - POSTGRES_HOST=${POSTGRES_HOST:-postgres} + - POSTGRES_PORT=${POSTGRES_PORT:-5432} + - DATABASE_URL=${DATABASE_URL:-postgresql://${POSTGRES_USER:-meme}:${POSTGRES_PASSWORD:-meme}@${POSTGRES_HOST:-postgres}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-meme_wrangler}} - MEMEBOT_BACKUP_DIR=${MEMEBOT_BACKUP_DIR:-/app/backups} volumes: - ./backups:/app/backups