diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..fc98bf2e1f0c916945f003419eb24ce875109320 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.venv +staticfiles/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..cae401b3e48e85b933157a52c5b7bc42a0820683 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +stages: + - build + +image: docker:20.10.8 + +variables: + DOCKER_TLS_CERTDIR: "/certs" + IMAGE_TAG_APP: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + IMAGE_TAG_NGINX: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-nginx + +services: + - docker:20.10.8-dind + +before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + +build_app: + stage: build + script: + - docker pull $CI_REGISTRY_IMAGE:test || true + - docker build --cache-from $CI_REGISTRY_IMAGE:test -t $IMAGE_TAG_APP . + - docker push $IMAGE_TAG_APP + +build_nginx: + stage: build + when: manual + script: + - docker pull $CI_REGISTRY_IMAGE:test-nginx || true + - docker build --cache-from $CI_REGISTRY_IMAGE:test-nginx -t $IMAGE_TAG_NGINX . -f Dockerfile.nginx + - docker push $IMAGE_TAG_NGINX diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..41366515096c6503c7e77927356a7c86270cca10 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.10 + +RUN mkdir /app +WORKDIR /app + +RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - +RUN apt-get install nodejs && rm -rf /var/lib/apt/lists/* + +COPY . . + +RUN pip install -r requirements/base.txt -r requirements/prod.txt +RUN npm install +RUN npm run build +RUN python manage.py collectstatic --noinput --settings=rybicka.settings.prod + +RUN bash -c "adduser --disabled-login --quiet --gecos app app && \ + chmod -R o+r /app/ && \ + chmod o+x /app/run.sh" +USER app + +ENV DJANGO_SETTINGS_MODULE "rybicka.settings.prod" + +EXPOSE 8000 + +CMD ["bash", "run.sh"] diff --git a/Dockerfile.nginx b/Dockerfile.nginx new file mode 100644 index 0000000000000000000000000000000000000000..47591546d366f11fba0362eff8a364dc8adf3c30 --- /dev/null +++ b/Dockerfile.nginx @@ -0,0 +1,3 @@ +FROM nginx:1.18 +EXPOSE 8080 +ADD nginx.conf /etc/nginx/conf.d/rybicka.conf diff --git a/Makefile b/Makefile index 0b53584209e8a36df2533979cf9dead37cd4184c..4dbde64bfe584eb70a83c33d63fe352f99d969a3 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PYTHON = python VENV = .venv PORT = 8012 -SETTINGS = rybicka.settings.dev +SETTINGS = rybicka.settings.prod .PHONY: help venv install build run shell migrations migrate @@ -26,7 +26,7 @@ venv: .venv/bin/python ${PYTHON} -m venv ${VENV} install: venv - ${VENV}/bin/pip install -r requirements/base.txt + ${VENV}/bin/pip install -r requirements/base.txt -r requirements/prod.txt ${VENV}/bin/nodeenv --python-virtualenv --node=19.3.0 ${VENV}/bin/npm install diff --git a/README.md b/README.md index 68e8f3c76570663953abefe16e7d253ae1f04358..8e019db7722fccfbfdea993a54aba06d049a2ef5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Aplikace pro všemožné jednoduché nástroje, které pomohou automatizovat složité úkony. -## Setup +## Lokální setup Požadavky: - Python 3.9+ diff --git a/env.example b/env.example index f1dd3ea0318034fdc38be0ca5c5f8a5dffc141da..5b8cb36653245921a245bda6313b083dc9a29e64 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,6 @@ DATABASE_URL="postgresql://rybicka:rybicka@localhost:5432/rybicka" SECRET_KEY="%@=^sip3=tqn6d_-xvvidc1@-t0t3&*kab@vr4c4" + +# Production settings +ALLOWED_HOSTS="nastroje.pirati.cz" diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000000000000000000000000000000000000..16484fd52069292cf24727fbf8e393ac8583608e --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,7 @@ +bind = "0.0.0.0:8000" +accesslog = "-" +workers = 1 +max_requests = 1000 +max_requests_jitter = 10 +timeout = 60 +graceful_timeout = 60 diff --git a/requirements/base.txt b/requirements/base.txt index ca71420615d1b84383887baf24b85d7bf1780a85..150dea4b81c612a8cca7f11e79a8ebfaec5a9ce5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ Django==4.1.5 django-database-url==1.0.3 -python-dotenv==0.21.0 +django-environ==0.9.0 psycopg2-binary==2.9.5 django-webpack-loader==1.8.0 nodeenv==1.7.0 diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000000000000000000000000000000000000..9655c9a0a7bb5c95b0bd3a2f3d344abbb9a9269e --- /dev/null +++ b/requirements/prod.txt @@ -0,0 +1,2 @@ +gunicorn==20.1.0 +whitenoise==6.3.0 diff --git a/run.sh b/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..5d9a7ada6bf4106d23769d523da59ae1e68e3720 --- /dev/null +++ b/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# exit on error +set -e + +# migrate database +python manage.py migrate + +# start webserver +exec gunicorn -c gunicorn.conf.py rybicka.wsgi diff --git a/rybicka/settings/base.py b/rybicka/settings/base.py index 92b18225f8d28effdb5f8d8921b931e7c8bb071f..8eb3f074efd718e15cae089d7e2d56c583680aa8 100644 --- a/rybicka/settings/base.py +++ b/rybicka/settings/base.py @@ -14,27 +14,26 @@ import os import pathlib import dj_database_url -import dotenv - -dotenv.load_dotenv() +import environ # Build paths inside the project like this: BASE_DIR / "subdir". BASE_DIR = pathlib.Path(__file__).parents[2] + +env = environ.Env() +environ.Env.read_env(os.path.join(BASE_DIR, ".env")) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ DEBUG = False # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ["SECRET_KEY"] +SECRET_KEY = env.str("SECRET_KEY") ALLOWED_HOSTS = [] -STATIC_ROOT = os.environ.get( - "STATIC_ROOT", - "staticfiles" -) +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") # Application definition diff --git a/rybicka/settings/prod.py b/rybicka/settings/prod.py new file mode 100644 index 0000000000000000000000000000000000000000..f3096fdebc72cd6a5c207ebce36484495a28d6c1 --- /dev/null +++ b/rybicka/settings/prod.py @@ -0,0 +1,12 @@ +""" +Production settings. +""" + +import os + +from .base import * + +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") + +MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" diff --git a/rybicka/wsgi.py b/rybicka/wsgi.py index 8cfda5e82e09cf76fb8b577626815099008ca2d6..93e488cef367cc8af528c0a4737fcbcc1eb3c8d5 100644 --- a/rybicka/wsgi.py +++ b/rybicka/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rybicka.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rybicka.settings.prod") application = get_wsgi_application() diff --git a/member_group_size_calc/static/member_group_size_calc/calculator.webp b/shared/static/shared/calculator.webp similarity index 100% rename from member_group_size_calc/static/member_group_size_calc/calculator.webp rename to shared/static/shared/calculator.webp diff --git a/shared/templates/shared/index.html b/shared/templates/shared/index.html index 4d196443a553c82eaaf0d8e7b9b29fb74a6f7bdf..ff6c42311ba3f11afc0ac261217204ff1b1e0222 100644 --- a/shared/templates/shared/index.html +++ b/shared/templates/shared/index.html @@ -18,7 +18,7 @@ <article class="card"> <a href="{% url "member_group_size_calc_index" %}"> <img - src="{% static "member_group_size_calc/calculator.webp" %}" + src="{% static "shared/calculator.webp" %}" alt="Kalkulačka výpočtu skupiny členů" class="w-full h-48 object-cover" >