diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 654bf3e..7855d10 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,5 +38,4 @@ jobs: name: rohanbatrain/second_brain_database username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - registry: ghcr.io - + registry: ghcr.io \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 42fa13b..e9836ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -# Use the official Python image -FROM python:3.9-slim +# Use a lightweight base image for production +FROM python:3.9-slim as base # Set the working directory inside the container WORKDIR /app @@ -10,6 +10,9 @@ COPY requirements.txt . # Install dependencies RUN pip install --no-cache-dir -r requirements.txt +# Install curl for health checks +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + # Create a non-root user and group named sbd_user with home directory RUN groupadd -r sbd_user && useradd -r -g sbd_user -d /sbd_user -m sbd_user @@ -25,8 +28,6 @@ RUN chown -R sbd_user:sbd_user /app # Install your package (assuming setup.py or pyproject.toml exists) RUN pip install . -RUN pip install second_brain_database - # Switch to the non-root user USER sbd_user @@ -36,6 +37,5 @@ ENV HOME=/sbd_user # Expose the port EXPOSE 5000 -# Run the app -CMD ["gunicorn", "--bind", "0.0.0.0:5000", "second_brain_database.main"] -# CMD ["python", "-m", "Second_Brain_Database.main"] \ No newline at end of file +# Use Gunicorn for production with optimized settings +CMD ["gunicorn", "--workers=3", "--threads=2", "--bind", "0.0.0.0:5000", "second_brain_database.main:app"] \ No newline at end of file diff --git a/dev-environment/docker-compose.yml b/dev-environment/docker-compose.yml index 93122fd..e1ba07b 100644 --- a/dev-environment/docker-compose.yml +++ b/dev-environment/docker-compose.yml @@ -28,6 +28,12 @@ services: networks: - proxy - default + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s nginx_proxy_manager: image: jc21/nginx-proxy-manager:latest diff --git a/docker-compose.yml b/docker-compose.yml index 728e28a..435b9fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,12 @@ services: networks: - proxy - default + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s nginx_proxy_manager: image: jc21/nginx-proxy-manager:latest diff --git a/src/second_brain_database/assets/banner.png b/src/second_brain_database/assets/banner.png deleted file mode 100644 index af9130f..0000000 Binary files a/src/second_brain_database/assets/banner.png and /dev/null differ diff --git a/src/second_brain_database/main.py b/src/second_brain_database/main.py index 65c1f2d..0b06d46 100644 --- a/src/second_brain_database/main.py +++ b/src/second_brain_database/main.py @@ -46,19 +46,42 @@ swallow_errors=True # Prevent Flask from logging too many limit errors ) +# Global whitelist for IPs +WHITELISTED_IPS = ["127.0.0.1", "localhost"] # Replace with your list of whitelisted IPs + @app.before_request def block_abusive_ips(): """ Block requests from IPs that are currently blocked in Redis. + Prevent blocking whitelisted IPs. Raises: 403: If the IP is blocked. """ - ip = request.remote_addr + ip = request.headers.get("X-Forwarded-For", request.remote_addr).split(",")[0].strip() + + if ip in WHITELISTED_IPS: + return # Skip blocking and logging for whitelisted IPs + blocked = r.get(f"blocked:{ip}") if blocked: print(f"Blocked request from IP: {ip}") abort(403, description="You are temporarily blocked.") +@app.before_request +def slow_down_attackers(): + """ + Introduce a delay for IPs with multiple failed attempts (tar-pitting). + """ + ip = request.remote_addr + + if ip in WHITELISTED_IPS: + return # Skip delay for whitelisted IPs + + failed_attempts = int(r.get(f"failed:{ip}") or 0) + if failed_attempts > 5: # Delay only if user has failed 5+ times + print(f"Delaying request from IP {ip} due to {failed_attempts} failed attempts.") + time.sleep(2) # 2-second delay before processing request + @app.after_request def track_failed_attempts(response): """ @@ -68,8 +91,12 @@ def track_failed_attempts(response): Returns: flask.Response: The response object. """ + ip = request.remote_addr + + if ip in WHITELISTED_IPS: + return response # Skip tracking for whitelisted IPs + if response.status_code == 429: # Too Many Requests - ip = request.remote_addr r.incr(f"failed:{ip}") # Increment failure count attempts = int(r.get(f"failed:{ip}") or 0) if attempts > 10: # Block IP after 10 failures @@ -77,17 +104,6 @@ def track_failed_attempts(response): print(f"IP {ip} blocked for 1 hour after {attempts} failures.") return response -@app.before_request -def slow_down_attackers(): - """ - Introduce a delay for IPs with multiple failed attempts (tar-pitting). - """ - ip = request.remote_addr - failed_attempts = int(r.get(f"failed:{ip}") or 0) - if failed_attempts > 5: # Delay only if user has failed 5+ times - print(f"Delaying request from IP {ip} due to {failed_attempts} failed attempts.") - time.sleep(2) # 2-second delay before processing request - @app.route("/") def landing_page(): """ @@ -104,7 +120,7 @@ def landing_page(): Welcome to Second Brain Database - +