From 01635ce62915a43dbfd22a35cd588b87add0751e Mon Sep 17 00:00:00 2001 From: Dior Date: Sat, 13 Jun 2026 17:26:21 +0100 Subject: [PATCH] Replace the wrong Django CI workflow with a correct Sanic CI matrix (Python 3.10-3.13) and a tag-triggered GHCR Docker build, add a Dockerfile that injects the server password at run time, and re-encode requirements.txt from UTF-16 to UTF-8. --- .dockerignore | 19 +++++++++ .github/workflows/ci.yml | 37 ++++++++++++++++++ .github/workflows/django.yml | 30 --------------- .github/workflows/docker-publish.yml | 55 +++++++++++++++++++++++++++ Dockerfile | 33 ++++++++++++++++ docker-entrypoint.sh | 16 ++++++++ requirements.txt | Bin 1254 -> 595 bytes 7 files changed, 160 insertions(+), 30 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/django.yml create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile create mode 100644 docker-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..30ab929 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.github +.gitignore +.idea +.pytest_cache +__pycache__ +**/__pycache__ +*.pyc +venv +i-try +tests +example.gif +*.md +*.MD +LICENSE +CONTRIBUTING.md +tests.bat +# Never copy keys or certs into the image. +*.pem diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e740758 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Minimum is 3.10: sanic 25.12.0 and markdown-it-py 4.0.0 both require >=3.10 + # (and cryptography 46.0.3 excludes 3.9.0/3.9.1). 3.13 is the highest version + # supported across every pinned dependency. + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: pytest tests/ -v diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml deleted file mode 100644 index 9766b45..0000000 --- a/.github/workflows/django.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Django CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - python-version: [3.7, 3.8, 3.9] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Run Tests - run: | - python manage.py test diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..ab785f7 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,55 @@ +name: Docker Publish + +# Build and push a Docker image to GHCR on every version tag (v1.2.3, v2.0.0, ...). +on: + push: + tags: + - "v*" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # Derive image tags from the git tag. + # v1.2.3 -> 1.2.3, 1.2, 1, plus a stable "latest". + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7c7ab7b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# syntax=docker/dockerfile:1 +FROM python:3.13-slim + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + HOST=0.0.0.0 \ + PORT=8000 + +WORKDIR /app + +# Install dependencies first so this layer is cached unless requirements change. +# All pinned deps ship manylinux wheels, so no compiler toolchain is needed. +COPY requirements.txt ./ +RUN python -m pip install --upgrade pip \ + && pip install -r requirements.txt + +# Copy application source. +COPY cmd_chat ./cmd_chat +COPY cmd_chat.py ./ +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +# Run as an unprivileged user. +RUN useradd --create-home --uid 10001 appuser +USER appuser + +# Server listens on $PORT (default 8000). +EXPOSE 8000 + +# No password is baked into the image. PASSWORD must be supplied at run time: +# docker run -e PASSWORD=secret -p 8000:8000 ghcr.io/diorwave/cmd-chat:latest +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..3bfd0d1 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh +set -e + +# Host and port have safe defaults but can be overridden at run time. +: "${HOST:=0.0.0.0}" +: "${PORT:=8000}" + +# The server password is a secret. It is never baked into the image; it must be +# provided at run time, for example: +# docker run -e PASSWORD=secret -p 8000:8000 ghcr.io/diorwave/cmd-chat:latest +if [ -z "${PASSWORD:-}" ]; then + echo "ERROR: PASSWORD environment variable is required (pass it at run time, e.g. -e PASSWORD=...)." >&2 + exit 1 +fi + +exec python cmd_chat.py serve "$HOST" "$PORT" --password "$PASSWORD" diff --git a/requirements.txt b/requirements.txt index 39a62e32bc0898ff78191e53a763015a72827431..08bd25238cc3c1bc2ba2361f20888268f7ff16e7 100644 GIT binary patch literal 595 zcmYjP%WlIU5WMp*Mg}`+d!SP9t(0p|5y8Y>)dmV{CG_jN>!eX{W_CP}1q<;EvoFTz zom8?FHiU>4{m@`djJNWMVCOT!8Hm<0QAwqRJ8yt&c|O@(JT@Wb#m?~KvoW3QWiMQ= z3FDY;nyNAVp6H#JaG7^#$B}tTcBC*N5--koOC?|kmPM2ji_Kp{yo3g@NvyJYF2kB< z4ydFS%Q_2Hf#p8p~tcDSIVUO|4@!ALKq3AdK=T@`q%!s zl`pIXS6+?c?U$VRyOmlC%$6<#aEna83%V!LY9N&49YPGe63TNQ9Le8@LO6Az6g$$R zz0GjyxZ|FP7k?_zeR;0Ma}rGzsphL8cjDhpjV(1eh_9rt`K@eq8z$b$^rY5`U#3R1 AWB>pF literal 1254 zcmZvcPjA{#5XARfDIdiMQ<64^RO&sIdhIDPhJuB$iR~zDKYW|t?D8Hoi4cEx-t5fm zKK}i!tg+sH^4i+QM!Vy&uxp!H$#ZU3_Q5Lac=R^$X#%yjg7@Ec%qQkxKV^K)?*oX& zd4qADS;brOP0Te2^$v{srJl9z=r*8e15@Gs2Rrav3;vIH(5a7{So$;g2G=#AcFXAs zjXQo3a%nftKI4qAJ9rPUUGrXn-eyiI6mPf}Zh}XgJ^08dIWC>6A(IZ4q^QK9dPwhJ zd#b%XgDB80H^;2$((?h_6ZqD7)ImxjsRNakv+C54+vFHII_ECd8t#|1wF~pZzS=jt&#L7vG>Q2U zciO%wZ=kAYO@n*h`RUTT z;(xq0^XbiIeD*Dt`^4MORUEPT=Xs6vIp&HIS)8UlQK@jGd!&syXCEpwr?`QkPtic{rt^$&IHIF+br;{|^WUv6H%YE2a`@%=|V5>oZI%IC^^T z`JAShIK-AVOI|eRFYrVXDJSh+sus8(`QE`OFG5E?$}r`sJ=WayZxpPT^OB3W@h|o* BxAy=5