From 39ee1e29453408275624f6d8d32e04187d2ee8dc Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 3 Mar 2026 20:38:32 +0100 Subject: [PATCH] chore: add Docker config files and fix fail2ban bind mount path --- .containerignore | 49 +++++++++++++++++ .dockerignore | 49 +++++++++++++++++ Docker/Dockerfile.backend | 69 ++++++++++++++++++++++++ Docker/Dockerfile.frontend | 45 ++++++++++++++++ Docker/compose.debug.yml | 2 +- Docker/compose.prod.yml | 102 +++++++++++++++++++++++++++++++++++ Docker/nginx.conf | 34 ++++++++++++ Docker/push.sh | 106 +++++++++++++++++++++++++++++++++++++ Docs/test.md | 1 + 9 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 .containerignore create mode 100644 .dockerignore create mode 100644 Docker/Dockerfile.backend create mode 100644 Docker/Dockerfile.frontend create mode 100644 Docker/compose.prod.yml create mode 100644 Docker/nginx.conf create mode 100644 Docker/push.sh create mode 100644 Docs/test.md diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000..65bbdf2 --- /dev/null +++ b/.containerignore @@ -0,0 +1,49 @@ +# ────────────────────────────────────────────── +# BanGUI — .dockerignore / .containerignore +# Works with both Docker and Podman. +# ────────────────────────────────────────────── + +# Version control +.git +.gitignore + +# Virtual environments +.venv +venv +env + +# IDE / editor +.vscode +.idea +*.swp +*.swo +*~ + +# Python caches +__pycache__ +*.pyc +*.pyo +.mypy_cache +.ruff_cache +.pytest_cache +.coverage +htmlcov + +# Node +frontend/node_modules +frontend/.vite + +# Build artifacts +dist +build +*.egg-info + +# Documentation (keep README at root if needed) +Docs + +# Tests (not needed in production images) +backend/tests + +# OS files +.DS_Store +Thumbs.db diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..65bbdf2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,49 @@ +# ────────────────────────────────────────────── +# BanGUI — .dockerignore / .containerignore +# Works with both Docker and Podman. +# ────────────────────────────────────────────── + +# Version control +.git +.gitignore + +# Virtual environments +.venv +venv +env + +# IDE / editor +.vscode +.idea +*.swp +*.swo +*~ + +# Python caches +__pycache__ +*.pyc +*.pyo +.mypy_cache +.ruff_cache +.pytest_cache +.coverage +htmlcov + +# Node +frontend/node_modules +frontend/.vite + +# Build artifacts +dist +build +*.egg-info + +# Documentation (keep README at root if needed) +Docs + +# Tests (not needed in production images) +backend/tests + +# OS files +.DS_Store +Thumbs.db diff --git a/Docker/Dockerfile.backend b/Docker/Dockerfile.backend new file mode 100644 index 0000000..e097e67 --- /dev/null +++ b/Docker/Dockerfile.backend @@ -0,0 +1,69 @@ +# ────────────────────────────────────────────────────────────── +# BanGUI — Backend image (Python / FastAPI) +# +# Compatible with Docker and Podman. +# Build context must be the project root. +# +# Usage: +# docker build -t bangui-backend -f Docker/Dockerfile.backend . +# podman build -t bangui-backend -f Docker/Dockerfile.backend . +# ────────────────────────────────────────────────────────────── + +# ── Stage 1: build dependencies ────────────────────────────── +FROM python:3.12-slim AS builder + +WORKDIR /build + +# Install build-time system dependencies +RUN apt-get update \ + && apt-get install -y --no-install-recommends gcc libffi-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY backend/pyproject.toml /build/ + +# Install Python dependencies into a virtual-env so we can copy it cleanly +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir . + +# ── Stage 2: runtime image ─────────────────────────────────── +FROM python:3.12-slim AS runtime + +LABEL maintainer="BanGUI" \ + description="BanGUI backend — fail2ban web management API" + +# Non-root user for security +RUN groupadd --gid 1000 bangui \ + && useradd --uid 1000 --gid bangui --shell /bin/bash --create-home bangui + +WORKDIR /app + +# Copy the pre-built virtual-env +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# Copy application source +COPY backend/app /app/app +COPY fail2ban-master /app/fail2ban-master + +# Data directory for the SQLite database +RUN mkdir -p /data && chown bangui:bangui /data +VOLUME ["/data"] + +# Default environment values (override at runtime) +ENV BANGUI_DATABASE_PATH="/data/bangui.db" \ + BANGUI_FAIL2BAN_SOCKET="/var/run/fail2ban/fail2ban.sock" \ + BANGUI_LOG_LEVEL="info" + +EXPOSE 8000 + +USER bangui + +# Health-check using the built-in health endpoint +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')" || exit 1 + +CMD ["uvicorn", "app.main:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"] diff --git a/Docker/Dockerfile.frontend b/Docker/Dockerfile.frontend new file mode 100644 index 0000000..0f2658c --- /dev/null +++ b/Docker/Dockerfile.frontend @@ -0,0 +1,45 @@ +# ────────────────────────────────────────────────────────────── +# BanGUI — Frontend image (React / Vite → nginx) +# +# Compatible with Docker and Podman. +# Build context must be the project root. +# +# Usage: +# docker build -t bangui-frontend -f Docker/Dockerfile.frontend . +# podman build -t bangui-frontend -f Docker/Dockerfile.frontend . +# ────────────────────────────────────────────────────────────── + +# ── Stage 1: install & build ───────────────────────────────── +FROM node:22-alpine AS builder + +WORKDIR /build + +# Install dependencies first (layer caching) +COPY frontend/package.json frontend/package-lock.json* /build/ +RUN npm ci --ignore-scripts + +# Copy source and build +COPY frontend/ /build/ +RUN npm run build + +# ── Stage 2: serve with nginx ──────────────────────────────── +FROM nginx:1.27-alpine AS runtime + +LABEL maintainer="BanGUI" \ + description="BanGUI frontend — fail2ban web management UI" + +# Remove default nginx content +RUN rm -rf /usr/share/nginx/html/* + +# Copy built assets +COPY --from=builder /build/dist /usr/share/nginx/html + +# Custom nginx config for SPA routing + API reverse proxy +COPY Docker/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ + CMD wget -qO /dev/null http://localhost:80/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Docker/compose.debug.yml b/Docker/compose.debug.yml index afa2727..d0e32c3 100644 --- a/Docker/compose.debug.yml +++ b/Docker/compose.debug.yml @@ -31,7 +31,7 @@ services: PUID: 0 PGID: 0 volumes: - - fail2ban-dev-config:/config + - ./fail2ban-dev-config:/config - fail2ban-dev-run:/var/run/fail2ban - /var/log:/var/log:ro healthcheck: diff --git a/Docker/compose.prod.yml b/Docker/compose.prod.yml new file mode 100644 index 0000000..2a1f354 --- /dev/null +++ b/Docker/compose.prod.yml @@ -0,0 +1,102 @@ +# ────────────────────────────────────────────────────────────── +# BanGUI — Production Compose +# +# Compatible with: +# docker compose -f Docker/compose.prod.yml up -d +# podman compose -f Docker/compose.prod.yml up -d +# podman-compose -f Docker/compose.prod.yml up -d +# +# Prerequisites: +# Create a .env file at the project root (or pass --env-file): +# BANGUI_SESSION_SECRET= +# ────────────────────────────────────────────────────────────── + +name: bangui + +services: + # ── fail2ban ───────────────────────────────────────────────── + fail2ban: + image: lscr.io/linuxserver/fail2ban:latest + container_name: bangui-fail2ban + restart: unless-stopped + cap_add: + - NET_ADMIN + - NET_RAW + network_mode: host + environment: + TZ: "${BANGUI_TIMEZONE:-UTC}" + PUID: 0 + PGID: 0 + volumes: + - fail2ban-config:/config + - fail2ban-run:/var/run/fail2ban + - /var/log:/var/log:ro + healthcheck: + test: ["CMD", "fail2ban-client", "ping"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + # ── Backend (FastAPI + uvicorn) ───────────────────────────── + backend: + build: + context: .. + dockerfile: Docker/Dockerfile.backend + container_name: bangui-backend + restart: unless-stopped + depends_on: + fail2ban: + condition: service_healthy + environment: + BANGUI_DATABASE_PATH: "/data/bangui.db" + BANGUI_FAIL2BAN_SOCKET: "/var/run/fail2ban/fail2ban.sock" + BANGUI_LOG_LEVEL: "info" + BANGUI_SESSION_SECRET: "${BANGUI_SESSION_SECRET:?Set BANGUI_SESSION_SECRET}" + BANGUI_TIMEZONE: "${BANGUI_TIMEZONE:-UTC}" + volumes: + - bangui-data:/data + - fail2ban-run:/var/run/fail2ban:ro + expose: + - "8000" + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"] + interval: 30s + timeout: 5s + start_period: 10s + retries: 3 + networks: + - bangui-net + + # ── Frontend (nginx serving built SPA + API proxy) ────────── + frontend: + build: + context: .. + dockerfile: Docker/Dockerfile.frontend + container_name: bangui-frontend + restart: unless-stopped + ports: + - "${BANGUI_PORT:-8080}:80" + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO", "/dev/null", "http://localhost:80/"] + interval: 30s + timeout: 5s + start_period: 5s + retries: 3 + networks: + - bangui-net + +volumes: + bangui-data: + driver: local + fail2ban-config: + driver: local + fail2ban-run: + driver: local + +networks: + bangui-net: + driver: bridge diff --git a/Docker/nginx.conf b/Docker/nginx.conf new file mode 100644 index 0000000..5910ccf --- /dev/null +++ b/Docker/nginx.conf @@ -0,0 +1,34 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # ── Gzip compression ───────────────────────────────────── + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + gzip_min_length 256; + + # ── API reverse proxy → backend container ───────────────── + location /api/ { + proxy_pass http://backend:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 60s; + } + + # ── Static assets with long-term caching ────────────────── + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # ── SPA fallback — serve index.html for client routes ───── + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/Docker/push.sh b/Docker/push.sh new file mode 100644 index 0000000..299d845 --- /dev/null +++ b/Docker/push.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# +# Build and push BanGUI container images to the Gitea registry. +# +# Usage: +# ./push.sh # builds & pushes with tag "latest" +# ./push.sh v1.2.3 # builds & pushes with tag "v1.2.3" +# ./push.sh v1.2.3 --no-build # pushes existing images only +# +# Prerequisites: +# podman login git.lpl-mind.de (or: docker login git.lpl-mind.de) + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- +REGISTRY="git.lpl-mind.de" +NAMESPACE="lukas.pupkalipinski" +PROJECT="bangui" + +BACKEND_IMAGE="${REGISTRY}/${NAMESPACE}/${PROJECT}/backend" +FRONTEND_IMAGE="${REGISTRY}/${NAMESPACE}/${PROJECT}/frontend" + +TAG="${1:-latest}" +SKIP_BUILD=false +if [[ "${2:-}" == "--no-build" ]]; then + SKIP_BUILD=true +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +log() { echo -e "\n>>> $*"; } +err() { echo -e "\nERROR: $*" >&2; exit 1; } + +# Detect container engine (podman preferred, docker fallback) +if command -v podman &>/dev/null; then + ENGINE="podman" +elif command -v docker &>/dev/null; then + ENGINE="docker" +else + err "Neither podman nor docker is installed." +fi + +# --------------------------------------------------------------------------- +# Pre-flight checks +# --------------------------------------------------------------------------- +echo "============================================" +echo " BanGUI — Build & Push" +echo " Engine : ${ENGINE}" +echo " Registry : ${REGISTRY}" +echo " Tag : ${TAG}" +echo "============================================" + +if [[ "${ENGINE}" == "podman" ]]; then + if ! podman login --get-login "${REGISTRY}" &>/dev/null; then + err "Not logged in. Run:\n podman login ${REGISTRY}" + fi +fi + +# --------------------------------------------------------------------------- +# Build +# --------------------------------------------------------------------------- +if [[ "${SKIP_BUILD}" == false ]]; then + log "Building backend image → ${BACKEND_IMAGE}:${TAG}" + "${ENGINE}" build \ + -t "${BACKEND_IMAGE}:${TAG}" \ + -f "${SCRIPT_DIR}/Dockerfile.backend" \ + "${PROJECT_ROOT}" + + log "Building frontend image → ${FRONTEND_IMAGE}:${TAG}" + "${ENGINE}" build \ + -t "${FRONTEND_IMAGE}:${TAG}" \ + -f "${SCRIPT_DIR}/Dockerfile.frontend" \ + "${PROJECT_ROOT}" +fi + +# --------------------------------------------------------------------------- +# Push +# --------------------------------------------------------------------------- +log "Pushing ${BACKEND_IMAGE}:${TAG}" +"${ENGINE}" push "${BACKEND_IMAGE}:${TAG}" + +log "Pushing ${FRONTEND_IMAGE}:${TAG}" +"${ENGINE}" push "${FRONTEND_IMAGE}:${TAG}" + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +echo "" +echo "============================================" +echo " Push complete!" +echo "" +echo " Images:" +echo " ${BACKEND_IMAGE}:${TAG}" +echo " ${FRONTEND_IMAGE}:${TAG}" +echo "" +echo " Deploy on server:" +echo " ${ENGINE} login ${REGISTRY}" +echo " ${ENGINE} compose -f Docker/compose.prod.yml pull" +echo " ${ENGINE} compose -f Docker/compose.prod.yml up -d" +echo "============================================" \ No newline at end of file diff --git a/Docs/test.md b/Docs/test.md new file mode 100644 index 0000000..dd35bcc --- /dev/null +++ b/Docs/test.md @@ -0,0 +1 @@ +https://lists.blocklist.de/lists/all.txt \ No newline at end of file