diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f074ffb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Dockerfile +db.sqlite3 +docker-compose.dev.yml +docker-compose.yml +nginx/ diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..a7b9556 --- /dev/null +++ b/.env.dev @@ -0,0 +1,11 @@ +DEBUG=1 +SQL_ENGINE=django.db.backends.postgresql +SQL_DATABASE=kcc3 +SQL_USER=kcc3 +SQL_PASSWORD=kcc3 +SQL_HOST=db +SQL_PORT=5432 +POSTGRES_USER=kcc3 +POSTGRES_PASSWORD=kcc3 +POSTGRES_DB=kcc3 +CELERY_BROKER=amqp://guest:guest@rabbitmq:5672// diff --git a/.env.prod.example b/.env.prod.example new file mode 100644 index 0000000..bb72a96 --- /dev/null +++ b/.env.prod.example @@ -0,0 +1,12 @@ +DEBUG=0 +SECRET_KEY=CHANGE_ME +DJANGO_ALLOWED_HOSTS=fanpai.chombo.club,yakuman.chombo.club +SQL_DATABASE=kcc3 +SQL_USER=kcc3 +SQL_PASSWORD=CHANGE_ME +SQL_HOST=db +SQL_PORT=5432 +POSTGRES_USER=kcc3 +POSTGRES_PASSWORD=CHANGE_ME +POSTGRES_DB=kcc3 +CELERY_BROKER=amqp://guest:guest@rabbitmq:5672// diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..a235b00 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,71 @@ +name: Docker Images + +on: + push: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-web: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/riichi/kcc3-web + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-and-push-proxy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/riichi/kcc3-proxy + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: nginx/ + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 222bb41..7593104 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,6 @@ coverage.xml # Django stuff: *.log -local_settings.py db.sqlite3 celerybeat.pid diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..de3327a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.11-slim-bookworm + +RUN mkdir -p /app/web +WORKDIR /app/web + +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +RUN apt-get update && \ + apt-get -y upgrade && \ + apt-get install -y netcat-openbsd zlib1g-dev libjpeg-dev gcc + +RUN pip install --upgrade pip +COPY ./requirements.txt . +RUN pip install -r requirements.txt + +COPY . . + +ENV MEDIA_ROOT /app/media +ENV STATIC_ROOT /app/static + +EXPOSE 8000 +ENTRYPOINT ["/app/web/entrypoint.sh"] +CMD ["gunicorn", "kcc3.wsgi:application", "--bind", "0.0.0.0:8000"] diff --git a/LICENSE b/LICENSE index ac78d33..d6b47a9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Mateusz Maćkowski +Copyright (c) 2019-2023 Kraków Chombo Club Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 830a672..9535f75 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # kcc3 +[![Docker Images Build Status](https://github.com/riichi/kcc3/workflows/Docker%20Images/badge.svg)](https://github.com/riichi/kcc3/actions/workflows/docker.yml) +[![License](https://shields.io/github/license/riichi/kcc3)](https://github.com/riichi/kcc3/blob/master/LICENSE) + KCC3 is a simple badge server developed for [Kraków Chombo Club](https://chombo.club/). It puts the focus on providing simple interface, automate as much as possible and provide features especially @@ -19,7 +22,6 @@ useful for Rīchi Mahjong players. ## Quickstart ``` pip install -r requirements.txt -cp kccbadgeserver/settings/{development_settings.py.example,local_settings.py} python manage.py migrate python manage.py runserver ``` diff --git a/badges/admin.py b/badges/admin.py index 61f47a9..fc957a7 100644 --- a/badges/admin.py +++ b/badges/admin.py @@ -67,7 +67,6 @@ def get_fieldsets(self, request, obj=None): 'fields': general_fields }), ('Endpoint', { - 'classes': ('collapse',), 'fields': ('endpoint_url', 'refresh_interval', 'token'), }), ) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..6ace7bf --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,64 @@ +x-env: + &env-file + - ./.env.dev + +services: + web: + build: + context: . + container_name: kcc3_web + volumes: + - static_volume:/app/static + - media_volume:/app/media + depends_on: + - db + - beat + env_file: *env-file + + worker: + build: + context: . + container_name: kcc3_worker + command: celery -A kcc3 worker --loglevel=info + depends_on: + - db + - rabbitmq + env_file: *env-file + + beat: + build: + context: . + container_name: kcc3_beat + command: celery -A kcc3 beat -S django --loglevel=info + depends_on: + - db + - rabbitmq + - worker + env_file: *env-file + + db: + image: postgres:16 + container_name: kcc3_db + volumes: + - postgres_data:/var/lib/postgresql/data/ + env_file: *env-file + + rabbitmq: + image: rabbitmq:3 + container_name: kcc3_rabbitmq + + proxy: + build: ./nginx + container_name: kcc3_proxy + volumes: + - static_volume:/app/static + - media_volume:/app/media + ports: + - 8000:80 + depends_on: + - web + +volumes: + postgres_data: + static_volume: + media_volume: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9d91510 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,56 @@ +x-env: + &env-file + - ./.env.prod + +services: + web: + image: ghcr.io/riichi/kcc3-web:master + container_name: kcc3_web + volumes: + - ./static_files:/app/static + - ./media_files:/app/media + depends_on: + - db + - beat + env_file: *env-file + + worker: + image: ghcr.io/riichi/kcc3-web:master + container_name: kcc3_worker + command: celery -A kcc3 worker --loglevel=info + depends_on: + - db + - rabbitmq + env_file: *env-file + + beat: + image: ghcr.io/riichi/kcc3-web:master + container_name: kcc3_beat + command: celery -A kcc3 beat -S django --loglevel=info + depends_on: + - db + - rabbitmq + - worker + env_file: *env-file + + db: + image: postgres:16 + container_name: kcc3_db + volumes: + - ./pgdata:/var/lib/postgresql/data/ + env_file: *env-file + + rabbitmq: + image: rabbitmq:3 + container_name: kcc3_rabbitmq + + proxy: + image: ghcr.io/riichi/kcc3-proxy:master + container_name: kcc3_proxy + volumes: + - ./static_files:/app/static + - ./media_files:/app/media + ports: + - 8000:80 + depends_on: + - web diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..b33e249 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +if [ "$SQL_ENGINE" = "django.db.backends.postgresql" ] +then + echo "Waiting for postgres..." + + while ! nc -z "$SQL_HOST" "$SQL_PORT"; do + sleep 0.1 + done + + echo "PostgreSQL started" +fi + +python manage.py migrate --noinput +python manage.py collectstatic --no-input --clear + +exec "$@" diff --git a/kcc3/celery.py b/kcc3/celery.py index 1e540ea..21150a3 100644 --- a/kcc3/celery.py +++ b/kcc3/celery.py @@ -3,7 +3,7 @@ from celery import Celery # set the default Django settings module for the 'celery' program. -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings.local_settings') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings.settings') app = Celery('kcc3') diff --git a/kcc3/settings/development_settings.py.example b/kcc3/settings/development_settings.py.example deleted file mode 100644 index 75659b1..0000000 --- a/kcc3/settings/development_settings.py.example +++ /dev/null @@ -1,31 +0,0 @@ -from .base import * - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '3on#7^-^=nxx0_npqt3r_gr6c#5))vdt!v5n876_vv78em9b!v' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [ - 'fanpai.localhost', - 'yakuman.localhost', -] -PARENT_HOST = 'localhost:8000' - - -# Database -# https://docs.djangoproject.com/en/2.2/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') -MEDIA_URL = '/media/' diff --git a/kcc3/settings/settings.py b/kcc3/settings/settings.py new file mode 100644 index 0000000..99dd50f --- /dev/null +++ b/kcc3/settings/settings.py @@ -0,0 +1,40 @@ +from .base import * + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = bool(os.environ.get('DEBUG', default=0)) + +ALLOWED_HOSTS = os.environ.get( + 'DJANGO_ALLOWED_HOSTS', + 'fanpai.localhost,yakuman.localhost,localhost' +).split(',') +PARENT_HOST = os.environ.get('DJANGO_PARENT_HOST', 'localhost:8000') + + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'), + 'NAME': os.environ.get('SQL_DATABASE', os.path.join(BASE_DIR, 'db.sqlite3')), + 'USER': os.environ.get('SQL_USER', None), + 'PASSWORD': os.environ.get('SQL_PASSWORD', None), + 'HOST': os.environ.get('SQL_HOST', None), + 'PORT': os.environ.get('SQL_PORT', None), + } +} + + +MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'media')) +MEDIA_URL = '/media/' +STATIC_ROOT = os.environ.get('STATIC_ROOT', os.path.join(BASE_DIR, 'static')) + +CELERY_BROKER_URL = os.environ.get( + 'CELERY_BROKER', 'amqp://guest:guest@localhost:5672//') diff --git a/manage.py b/manage.py index 1435140..9ff183a 100755 --- a/manage.py +++ b/manage.py @@ -5,10 +5,7 @@ def main(): - os.environ.setdefault( - 'DJANGO_SETTINGS_MODULE', - 'kcc3.settings.local_settings' - ) + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'kcc3.settings.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..c1d1b98 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:alpine + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000..0f9092e --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,22 @@ +upstream kcc3 { + server web:8000; +} + +server { + listen 80; + + location / { + proxy_pass http://kcc3; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location /static/ { + alias /app/static/; + } + + location /media/ { + alias /app/media/; + } +} diff --git a/requirements.txt b/requirements.txt index b4758bd..84ce305 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,9 @@ Django==3.2.11 django-celery-beat==2.2.1 django-hosts==5.1 djangorestframework==3.13.1 +gunicorn==21.2.0 Pillow==9.0.0 +psycopg2-binary==2.9.9 pytz==2021.3 requests==2.27.1 sqlparse==0.4.2