Compare commits

..

4 Commits

Author SHA1 Message Date
Andre Beging
79a626923a Add test 2025-10-21 12:56:16 +02:00
Andre Beging
a283673bcf feat: add script for building and pushing Docker images with tagging options 2025-10-09 11:35:25 +02:00
Andre Beging
5c22415ab4 python alpine base image for image creation 2025-10-09 11:28:54 +02:00
Andre Beging
a29ce99420 refactor: remove startup announcement logic from on_startup function 2025-10-09 11:10:32 +02:00
6 changed files with 118 additions and 28 deletions

View File

@@ -1,17 +1,16 @@
# Multi-stage für kleinere finale Imagegröße
FROM python:3.12-slim AS base
# Alpine-basiertes Image für kleinere Größe
FROM python:3.12-alpine AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# System deps (tzdata optional falls benötigt)
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Systemabhängigkeiten installieren (su-exec für Rechtewechsel)
RUN apk add --no-cache ca-certificates su-exec
# Non-root user
RUN useradd -u 10001 -m appuser
RUN addgroup -S appgroup \
&& adduser -S -G appgroup -u 10001 appuser
WORKDIR /app

View File

@@ -79,6 +79,21 @@ Alle Counter werden nach jeder Änderung in `/data/counters.json` gespeichert. D
- Non-root User `appuser`
- Kein Port-Expose (Long Polling)
## Docker Image veröffentlichen
Zum Bauen und Pushen des Images mit `latest`-Tag und einem datierten Tag (z.B. `2025-09-30`) steht das Skript `publish.py` bereit. Stelle vorher sicher, dass du in der Registry `git.beging.de` angemeldet bist.
```bash
python publish.py --date 2025-09-30
```
Ohne `--date` wird automatisch das heutige UTC-Datum verwendet. Mit `--dry-run` kannst du die Docker-Kommandos nur anzeigen lassen:
```bash
python publish.py --dry-run
```
Falls du den `latest` Tag nicht veröffentlichen möchtest, verwende `--no-latest`.
## Logging
Standard: INFO. Anpassbar über `log_level` in der Config.

18
bot.py
View File

@@ -405,24 +405,6 @@ def init_counters(existing: Dict[str, int], config: Dict[str, Any]) -> Dict[str,
async def on_startup(app):
logger.info("Bot gestartet und bereit.")
announce_ids = os.environ.get("STARTUP_ANNOUNCE_CHAT_IDS")
if announce_ids:
ids = []
for raw in announce_ids.split(','):
raw = raw.strip()
if not raw:
continue
try:
ids.append(int(raw))
except ValueError:
logger.warning("Kann Chat ID %s nicht in int umwandeln", raw)
if ids:
me = await app.bot.get_me()
for cid in ids:
try:
await app.bot.send_message(cid, f"🤖 {me.first_name} ist bereit. Nutze /help für Befehle.")
except Exception as e:
logger.warning("Konnte Startup-Nachricht an %s nicht senden: %s", cid, e)
def main():

View File

@@ -4,13 +4,13 @@ set -e
# If running as root, fix ownership of /data, then drop privileges
if [ "$(id -u)" = "0" ]; then
mkdir -p /data
chown -R appuser:appuser /data || echo "Warn: could not chown /data"
chown -R appuser:appgroup /data || echo "Warn: could not chown /data"
# Copy example config only if missing target
if [ ! -f /app/config.yaml ] && [ -f /app/config.example.yaml ]; then
cp /app/config.example.yaml /app/config.yaml
chown appuser:appuser /app/config.yaml || true
chown appuser:appgroup /app/config.yaml || true
fi
exec su -s /bin/sh appuser -c "$*"
exec su-exec appuser "$@"
else
exec "$@"
fi

93
publish.py Normal file
View File

@@ -0,0 +1,93 @@
import argparse
import datetime as dt
import shlex
import subprocess
import sys
from pathlib import Path
REPOSITORY = "git.beging.de/troogs/gigalativbot"
DEFAULT_CONTEXT = Path(__file__).parent
def run_command(cmd: list[str], /, *, dry_run: bool) -> None:
"""Print and optionally execute a shell command."""
print("$", shlex.join(cmd))
if dry_run:
return
subprocess.run(cmd, check=True)
def build_and_push(*, include_latest: bool, date_tag: str, context: Path, dockerfile: Path | None, dry_run: bool) -> None:
tags: list[str] = []
if include_latest:
tags.append(f"{REPOSITORY}:latest")
tags.append(f"{REPOSITORY}:{date_tag}")
build_cmd = ["docker", "build"]
for tag in tags:
build_cmd.extend(["-t", tag])
if dockerfile is not None:
build_cmd.extend(["-f", str(dockerfile)])
build_cmd.append(str(context))
run_command(build_cmd, dry_run=dry_run)
for tag in tags:
run_command(["docker", "push", tag], dry_run=dry_run)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Build and push docker images with latest and YYYYMMDD tags.")
parser.add_argument("--context", default=DEFAULT_CONTEXT, type=Path, help="Build context directory (default: project root)")
parser.add_argument("--dockerfile", type=Path, default=None, help="Path to Dockerfile (defaults to <context>/Dockerfile)")
parser.add_argument("--date", help="Custom date string for tag (YYYY-MM-DD). Defaults to today (UTC).")
parser.add_argument("--no-latest", action="store_true", help="Do not tag/push the 'latest' tag.")
parser.add_argument("--dry-run", action="store_true", help="Print the docker commands without executing them.")
return parser.parse_args()
def ensure_date_tag(raw: str | None) -> str:
if raw is None:
return dt.datetime.utcnow().strftime("%Y-%m-%d")
try:
parsed = dt.datetime.strptime(raw, "%Y-%m-%d")
except ValueError as exc:
raise ValueError("Date tag must be in YYYY-MM-DD format.") from exc
return parsed.strftime("%Y-%m-%d")
def main() -> int:
args = parse_args()
try:
date_tag = ensure_date_tag(args.date)
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
context = args.context.resolve()
dockerfile = args.dockerfile.resolve() if args.dockerfile else None
if dockerfile and not dockerfile.exists():
print(f"Error: Dockerfile {dockerfile} does not exist.", file=sys.stderr)
return 2
if not context.exists():
print(f"Error: context directory {context} does not exist.", file=sys.stderr)
return 3
try:
build_and_push(
include_latest=not args.no_latest,
date_tag=date_tag,
context=context,
dockerfile=dockerfile,
dry_run=args.dry_run,
)
except subprocess.CalledProcessError as exc:
print(f"Command failed with exit code {exc.returncode}: {exc.cmd}", file=sys.stderr)
return exc.returncode
return 0
if __name__ == "__main__":
sys.exit(main())

1
test.txt Normal file
View File

@@ -0,0 +1 @@
.