diff --git a/.dockerignore b/.dockerignore index e7d9050..9d404b0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,17 +1,20 @@ # Git -.git +.git/ .gitignore .gitattributes +.gitmodules # IDE -.vscode -.idea +.vscode/ +.idea/ *.swp *.swo +*~ # OS .DS_Store Thumbs.db +desktop.ini # Python __pycache__/ @@ -19,23 +22,66 @@ __pycache__/ *$py.class *.so .Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +pip-log.txt +pip-delete-this-directory.txt + +# Virtual environments .env .venv +env/ venv/ ENV/ -env/ +env.bak/ +venv.bak/ + +# Test and coverage +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ # Logs logs/ *.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* -# Test and coverage -.coverage -htmlcov/ -.pytest_cache/ -.tox/ +# Kodi addon specific +resources/settings.xml~ +resources/language/*/strings.xml~ +addon.xml~ -# Distribution -dist/ -build/ -*.egg-info/ \ No newline at end of file +# Docker +docker-compose.override.yml +.env +*.env.local + +# Temporary files +tmp/ +temp/ \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..f218bc7 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,89 @@ +name: Docker Build and Publish + +on: + push: + branches: [ master, main ] + tags: [ 'v*' ] + pull_request: + branches: [ master, main ] + workflow_dispatch: # Manual trigger + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install pytest pytest-asyncio + + - name: Run tests (if any) + run: | + # Add your test commands here + # Example: python -m pytest tests/ -v + echo "No tests configured yet" + + docker: + needs: test + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: nirvana7777/ultimate-backend + tags: | + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + build-args: | + APP_USER=ultimate + + notify: + runs-on: ubuntu-latest + needs: [test, docker] + if: always() + steps: + - name: Workflow status + run: | + echo "Test job: ${{ needs.test.result }}" + echo "Docker job: ${{ needs.docker.result }}" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7fbc481..612de89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,25 @@ -# Use Python 3.11 slim as base +# Ultimate Backend Streaming Service - Kodi addon in standalone Docker mode FROM python:3.11-slim -# Set environment variables +# Build arguments for user/group configuration +ARG USER_ID=1000 +ARG GROUP_ID=1000 +ARG APP_USER=ultimate +ARG APP_HOME=/app + +# Environment variables ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ TZ=UTC \ ULTIMATE_PORT=7777 \ ULTIMATE_COUNTRY=DE \ - PYTHONPATH=/app/lib:$PYTHONPATH - -# Create non-root user -RUN useradd -m -u 1000 -s /bin/bash kodi + ULTIMATE_DEBUG=false \ + PYTHONPATH=${APP_HOME}/lib:$PYTHONPATH \ + HOME=/home/${APP_USER} \ + PATH=/home/${APP_USER}/.local/bin:$PATH # Install system dependencies -RUN apt-get update && apt-get install -y \ +RUN apt-get update && apt-get install -y --no-install-recommends \ gcc \ g++ \ libxml2-dev \ @@ -23,58 +29,93 @@ RUN apt-get update && apt-get install -y \ libxmlsec1-dev \ pkg-config \ curl \ - && rm -rf /var/lib/apt/lists/* + ca-certificates \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Create application user and group with configurable UID/GID +RUN groupadd -g ${GROUP_ID} ${APP_USER} && \ + useradd -u ${USER_ID} -g ${APP_USER} -m -s /bin/bash ${APP_USER} # Set working directory -WORKDIR /app +WORKDIR ${APP_HOME} -# Copy entire project -COPY . /app/ - -# Create config directory and ensure proper permissions -RUN mkdir -p /config && \ - chown -R kodi:kodi /app /config - -USER kodi +# Copy requirements first for better caching +COPY requirements.txt /tmp/requirements.txt # Install Python dependencies -RUN pip install --no-cache-dir --user \ - bottle \ - requests \ - lxml \ - defusedxml \ - m3u8 \ - iso8601 \ - pycountry \ - python-dateutil \ - urllib3 \ - chardet \ - pycryptodome \ - cryptography +# If requirements.txt exists, use it. Otherwise install defaults. +RUN if [ -f /tmp/requirements.txt ]; then \ + echo "Installing from requirements.txt..." && \ + pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r /tmp/requirements.txt; \ + else \ + echo "No requirements.txt found, installing default packages..." && \ + pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir \ + bottle>=0.12.25 \ + requests>=2.31.0 \ + lxml>=4.9.3 \ + defusedxml>=0.7.1 \ + m3u8>=4.0.0 \ + iso8601>=1.1.0 \ + pycountry>=23.12.11 \ + python-dateutil>=2.8.2 \ + urllib3>=2.0.7 \ + chardet>=5.2.0 \ + pycryptodome>=3.19.0 \ + cryptography>=41.0.0; \ + fi -# Create requirements.txt for documentation -RUN echo "bottle>=0.12.25" > /app/requirements.txt && \ - echo "requests>=2.31.0" >> /app/requirements.txt && \ - echo "lxml>=4.9.3" >> /app/requirements.txt && \ - echo "defusedxml>=0.7.1" >> /app/requirements.txt && \ - echo "m3u8>=4.0.0" >> /app/requirements.txt && \ - echo "iso8601>=1.1.0" >> /app/requirements.txt && \ - echo "pycountry>=23.12.11" >> /app/requirements.txt && \ - echo "python-dateutil>=2.8.2" >> /app/requirements.txt && \ - echo "urllib3>=2.0.7" >> /app/requirements.txt && \ - echo "chardet>=5.2.0" >> /app/requirements.txt && \ - echo "pycryptodome>=3.19.0" >> /app/requirements.txt && \ - echo "cryptography>=41.0.0" >> /app/requirements.txt +# Copy application code +COPY --chown=${USER_ID}:${GROUP_ID} . ${APP_HOME}/ + +# Create necessary directories +RUN mkdir -p /config /logs /cache && \ + chown -R ${USER_ID}:${GROUP_ID} /config /logs /cache + +# Create a requirements.txt if it doesn't exist (for documentation) +RUN if [ ! -f requirements.txt ]; then \ + echo "# Ultimate Backend Service Requirements" > requirements.txt && \ + echo "# Auto-generated for documentation" >> requirements.txt && \ + echo "" >> requirements.txt && \ + echo "bottle>=0.12.25" >> requirements.txt && \ + echo "requests>=2.31.0" >> requirements.txt && \ + echo "lxml>=4.9.3" >> requirements.txt && \ + echo "defusedxml>=0.7.1" >> requirements.txt && \ + echo "m3u8>=4.0.0" >> requirements.txt && \ + echo "iso8601>=1.1.0" >> requirements.txt && \ + echo "pycountry>=23.12.11" >> requirements.txt && \ + echo "python-dateutil>=2.8.2" >> requirements.txt && \ + echo "urllib3>=2.0.7" >> requirements.txt && \ + echo "chardet>=5.2.0" >> requirements.txt && \ + echo "pycryptodome>=3.19.0" >> requirements.txt && \ + echo "cryptography>=41.0.0" >> requirements.txt; \ + fi + +# Switch to non-root user +USER ${USER_ID} + +# Create default config if not provided +RUN if [ ! -f /config/config.json ]; then \ + echo '{"default_country": "DE", "server_port": 7777, "debug_mode": false}' > /config/config.json; \ + fi # Expose the service port EXPOSE ${ULTIMATE_PORT} # Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD curl -f http://localhost:${ULTIMATE_PORT}/api/providers || exit 1 +# Labels for better image metadata +LABEL maintainer="nirvana-7777" \ + description="Ultimate Backend Streaming Service" \ + version="1.0.0" \ + org.opencontainers.image.source="https://github.com/nirvana-7777/script.service.ultimate" + # Run in standalone mode (since Kodi isn't available in Docker) ENTRYPOINT ["python", "/app/service.py"] -# Default command +# Default command with configurable options CMD ["--standalone", "--config-dir", "/config"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7af70e5..a386ff4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,12 @@ version: '3.8' services: ultimate-backend: - build: . + build: + context: . + args: + - USER_ID=1000 + - GROUP_ID=1000 + image: nirvana7777/ultimate-backend:latest container_name: ultimate-backend restart: unless-stopped ports: @@ -10,8 +15,8 @@ services: environment: - ULTIMATE_PORT=7777 - ULTIMATE_COUNTRY=DE - - TZ=Europe/Berlin - - DEBUG_MODE=true + - ULTIMATE_DEBUG=false + - TZ=${TZ:-Europe/Berlin} volumes: - ./config:/config - ./logs:/logs @@ -19,10 +24,16 @@ services: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:7777/api/providers"] interval: 30s - timeout: 3s + timeout: 10s retries: 3 - start_period: 5s + start_period: 10s + networks: + - ultimate-network labels: - "traefik.enable=true" - "traefik.http.routers.ultimate.rule=Host(`ultimate.local`)" - - "traefik.http.services.ultimate.loadbalancer.server.port=7777" \ No newline at end of file + - "traefik.http.services.ultimate.loadbalancer.server.port=7777" + +networks: + ultimate-network: + driver: bridge \ No newline at end of file