feat(docker): enhance Dockerfile with debugging outputs and install tree utility; update .dockerignore and MANIFEST.in for better packaging

This commit is contained in:
pacnpal
2025-01-28 22:36:22 -05:00
parent 64d09b8842
commit 0bc9dded41
6 changed files with 107 additions and 57 deletions

View File

@@ -42,6 +42,3 @@ htmlcov/
# Project specific # Project specific
rules_backup/ rules_backup/
# Documentation
*.md

View File

@@ -12,6 +12,7 @@ RUN apt-get update && \
libc6-dev \ libc6-dev \
python3-dev \ python3-dev \
python3-pip \ python3-pip \
tree \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& python3 -m pip install --no-cache-dir --upgrade "pip>=21.3" setuptools wheel && python3 -m pip install --no-cache-dir --upgrade "pip>=21.3" setuptools wheel
@@ -26,23 +27,27 @@ RUN mkdir -p /app/src/simpleguardhome && \
# Copy source code, maintaining directory structure # Copy source code, maintaining directory structure
COPY . /app/ COPY . /app/
# Set execute permission for entrypoint script # Debug: Show the copied files and set execute permission for entrypoint script
RUN chmod +x /app/docker-entrypoint.sh && \ RUN echo "Project structure:" && \
tree /app && \
echo "Package directory contents:" && \
ls -la /app/src/simpleguardhome/ && \
chmod +x /app/docker-entrypoint.sh && \
cp /app/docker-entrypoint.sh /usr/local/bin/ cp /app/docker-entrypoint.sh /usr/local/bin/
# Set PYTHONPATH # Set PYTHONPATH
ENV PYTHONPATH=/app/src ENV PYTHONPATH=/app/src
# Install Python requirements # Install Python requirements and verify the package
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt && \
set -e && \
# Install and verify the package
RUN set -e && \
echo "Installing package..." && \ echo "Installing package..." && \
pip uninstall -y simpleguardhome || true && \ pip uninstall -y simpleguardhome || true && \
# Verify source files exist # Debug: Show package files
echo "Verifying source files..." && \ echo "Python path:" && \
ls -la /app/src/simpleguardhome/ && \ python3 -c "import sys; print('\n'.join(sys.path))" && \
echo "Source directory contents:" && \
ls -R /app/src && \
# Install package in editable mode with compatibility mode enabled # Install package in editable mode with compatibility mode enabled
pip install --use-pep517 -e . --config-settings editable_mode=compat && \ pip install --use-pep517 -e . --config-settings editable_mode=compat && \
echo "Verifying installation..." && \ echo "Verifying installation..." && \
@@ -50,13 +55,15 @@ RUN set -e && \
# List all package files # List all package files
echo "Package contents:" && \ echo "Package contents:" && \
find /app/src/simpleguardhome -type f -ls && \ find /app/src/simpleguardhome -type f -ls && \
# Verify import works # Verify package can be imported
echo "Testing import..." && \ echo "Testing import..." && \
python3 -c "import simpleguardhome; from simpleguardhome.main import app; print(f'Package found at: {simpleguardhome.__file__}')" && \ python3 -c "import simpleguardhome; print(f'Package found at: {simpleguardhome.__file__}')" && \
echo "Package installation successful" # Verify app can be imported
echo "Testing app import..." && \
python3 -c "from simpleguardhome.main import app; print('App imported successfully')" && \
echo "Package installation successful" && \
# Create rules backup directory with proper permissions # Create rules backup directory with proper permissions
RUN mkdir -p /app/rules_backup && \ mkdir -p /app/rules_backup && \
chmod 777 /app/rules_backup chmod 777 /app/rules_backup
# Default environment variables # Default environment variables

View File

@@ -1,3 +1,18 @@
recursive-include src/simpleguardhome/templates * # Include all package Python files
graft src/simpleguardhome
# Include package data files
include src/simpleguardhome/favicon.ico include src/simpleguardhome/favicon.ico
recursive-include src/simpleguardhome *.py include src/simpleguardhome/templates/*.html
# Include important project files
include README.md
include LICENSE
include requirements.txt
include pyproject.toml
include setup.py
# Exclude bytecode files
global-exclude *.py[cod]
global-exclude __pycache__
global-exclude *.so

View File

@@ -22,9 +22,21 @@ dependencies = [
] ]
[tool.setuptools] [tool.setuptools]
# Using explicit package configuration
package-dir = {"" = "src"} package-dir = {"" = "src"}
packages = ["simpleguardhome"] packages = ["simpleguardhome"]
include-package-data = true
# Include all package data
[tool.setuptools.package-data] [tool.setuptools.package-data]
simpleguardhome = ["templates/*", "favicon.ico"] "*" = ["*.ico", "templates/*.html"]
# Explicitly include the package data
[options.package_data]
simpleguardhome = [
"templates/*",
"favicon.ico"
]
# Make sure data files are included
[options]
include_package_data = true

View File

@@ -1,6 +1,7 @@
from pydantic_settings import BaseSettings
from typing import Optional from typing import Optional
from pydantic_settings import BaseSettings # type: ignore
class Settings(BaseSettings): class Settings(BaseSettings):
"""Application settings using environment variables.""" """Application settings using environment variables."""

View File

@@ -1,25 +1,33 @@
from fastapi import FastAPI, Request, Form, HTTPException, status
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pathlib import Path
import httpx
import logging import logging
from pathlib import Path
from typing import Dict from typing import Dict
from . import adguard
from .config import settings import httpx # noqa: F401
from .adguard import ( from fastapi import ( # type: ignore # noqa: F401
AdGuardError, FastAPI,
AdGuardConnectionError, Form,
AdGuardAPIError, HTTPException,
AdGuardValidationError, Request,
FilterStatus, status,
FilterCheckHostResponse,
SetRulesRequest
) )
from fastapi.middleware.cors import CORSMiddleware # type: ignore
from fastapi.responses import HTMLResponse, JSONResponse # type: ignore
from fastapi.staticfiles import StaticFiles # type: ignore
from fastapi.templating import Jinja2Templates # type: ignore
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from . import adguard
from .adguard import (
AdGuardAPIError,
AdGuardConnectionError,
AdGuardError,
AdGuardValidationError,
FilterCheckHostResponse,
FilterStatus,
SetRulesRequest,
)
from .config import settings # noqa: F401
# Configure logging # Configure logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -53,13 +61,17 @@ app.mount("/static", StaticFiles(directory=str(Path(__file__).parent)), name="st
# Mount favicon.ico at root # Mount favicon.ico at root
static_files_path = Path(__file__).parent static_files_path = Path(__file__).parent
app.mount("/favicon.ico", StaticFiles(directory=str(static_files_path)), name="favicon") app.mount("/favicon.ico",
StaticFiles(directory=str(static_files_path)), name="favicon")
# Response models matching AdGuard spec # Response models matching AdGuard spec
class ErrorResponse(BaseModel): class ErrorResponse(BaseModel):
"""Error response model according to AdGuard spec.""" """Error response model according to AdGuard spec."""
message: str = Field(..., description="The error message") message: str = Field(..., description="The error message")
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def home(request: Request): async def home(request: Request):
"""Render the home page.""" """Render the home page."""
@@ -68,6 +80,7 @@ async def home(request: Request):
{"request": request} {"request": request}
) )
@app.get( @app.get(
"/control/filtering/check_host", "/control/filtering/check_host",
response_model=FilterCheckHostResponse, response_model=FilterCheckHostResponse,
@@ -96,11 +109,12 @@ async def check_domain(name: str) -> FilterCheckHostResponse:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e) detail=str(e)
) ) from e
except Exception as e: except Exception as e:
logger.error(f"Error checking domain {name}: {str(e)}") logger.error(f"Error checking domain {name}: {str(e)}")
raise raise
@app.post( @app.post(
"/control/filtering/set_rules", "/control/filtering/set_rules",
response_model=Dict, response_model=Dict,
@@ -144,11 +158,12 @@ async def add_to_whitelist(request: SetRulesRequest) -> Dict:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e) detail=str(e)
) ) from e
except Exception as e: except Exception as e:
logger.error(f"Error adding domain to whitelist: {str(e)}") logger.error(f"Error adding domain to whitelist: {str(e)}")
raise raise
@app.get( @app.get(
"/control/filtering/status", "/control/filtering/status",
response_model=FilterStatus, response_model=FilterStatus,
@@ -167,8 +182,9 @@ async def get_filtering_status() -> FilterStatus:
logger.error(f"Error getting filter status: {str(e)}") logger.error(f"Error getting filter status: {str(e)}")
raise raise
@app.exception_handler(AdGuardError) @app.exception_handler(AdGuardError)
async def adguard_exception_handler(request: Request, exc: AdGuardError) -> JSONResponse: async def adguard_exception_handler(_request: Request, exc: AdGuardError) -> JSONResponse:
"""Handle AdGuard-related exceptions according to spec.""" """Handle AdGuard-related exceptions according to spec."""
if isinstance(exc, AdGuardConnectionError): if isinstance(exc, AdGuardConnectionError):
status_code = status.HTTP_503_SERVICE_UNAVAILABLE status_code = status.HTTP_503_SERVICE_UNAVAILABLE
@@ -184,9 +200,10 @@ async def adguard_exception_handler(request: Request, exc: AdGuardError) -> JSON
content={"message": str(exc)} content={"message": str(exc)}
) )
def start(): def start():
"""Start the application using uvicorn.""" """Start the application using uvicorn."""
import uvicorn import uvicorn # type: ignore
uvicorn.run( uvicorn.run(
app, app,
host="0.0.0.0", host="0.0.0.0",
@@ -194,5 +211,6 @@ def start():
reload=False # Disable reload in Docker reload=False # Disable reload in Docker
) )
if __name__ == "__main__": if __name__ == "__main__":
start() start()