Merge pull request #4 from overspend1/codex/fix-dockerfile-and-compose-for-meme-wrangler-w3lgdz

fix: route compose db connections to postgres service
This commit was merged in pull request #4.
This commit is contained in:
Wiktor
2025-11-01 22:54:12 +01:00
committed by GitHub
5 changed files with 71 additions and 3 deletions

View File

@@ -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=

View File

@@ -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

View File

@@ -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=<sha256 hash of your backup secret>

59
bot.py
View File

@@ -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<id>

View File

@@ -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