diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..77a43ceb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,91 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2021-present Kaleidos Ventures SL + +FROM python:3.11-slim +LABEL maintainer="support@taiga.io" + +# Avoid prompting for configuration +ENV DEBIAN_FRONTEND=noninteractive + +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONFAULTHANDLER=1 + +# Use a virtualenv +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +# Get the code +COPY . /taiga-back +WORKDIR /taiga-back + +# grab gosu for easy step-down from root +# https://github.com/tianon/gosu/blob/master/INSTALL.md +ENV GOSU_VERSION 1.12 + +RUN set -eux; \ + apt-get update; \ + # install system dependencies + apt-get install -y \ + build-essential \ + gettext \ + # libpq5 needed in runtime for psycopg2 + libpq5 \ + libpq-dev \ + git \ + net-tools \ + procps \ + wget; \ + # install gosu + apt-get install -y --no-install-recommends ca-certificates wget; \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + chmod +x /usr/local/bin/gosu; \ + # verify gosu signature + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + command -v gpgconf && gpgconf --kill all || :; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + # install Taiga dependencies + python -m pip install --upgrade pip; \ + python -m pip install wheel; \ + python -m pip install -r requirements.txt; \ + python -m pip install -r requirements-contribs.txt; \ + python manage.py compilemessages; \ + python manage.py collectstatic --no-input; \ + chmod +x docker/entrypoint.sh; \ + chmod +x docker/async_entrypoint.sh; \ + cp docker/config.py settings/config.py; \ + # create taiga group and user to use it and give permissions over the code (in entrypoint) + groupadd --system taiga --gid=999; \ + useradd --system --no-create-home --gid taiga --uid=999 --shell=/bin/bash taiga; \ + mkdir -p /taiga-back/media/exports; \ + chown -R taiga:taiga /taiga-back; \ + # remove unneeded files and packages + apt-get purge -y \ + build-essential \ + gettext \ + git \ + libpq-dev \ + net-tools \ + procps \ + wget; \ + apt-get autoremove -y; \ + rm -rf /var/lib/apt/lists/*; \ + rm -rf /root/.cache; \ + # clean taiga + rm requirements.txt; \ + rm requirements-contribs.txt; \ + find . -name '__pycache__' -exec rm -r '{}' +; \ + find . -name '*pyc' -exec rm -r '{}' +; \ + find . -name '*po' -exec rm -r '{}' + + +ENV DJANGO_SETTINGS_MODULE=settings.config + +EXPOSE 8000 +ENTRYPOINT ["./docker/entrypoint.sh"] diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..5e6e0c84 --- /dev/null +++ b/Pipfile @@ -0,0 +1,97 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +aggdraw = "==1.3.15" +amqp = "==5.1.1" +asana = "==3.1.0" +asgiref = "==3.6.0" +async-timeout = "==4.0.2" +attrs = "==22.2.0" +backoff = "==1.6.0" +billiard = "==3.6.4.0" +bleach = "==4.1.0" +cairocffi = "==1.4.0" +cairosvg = "==2.6.0" +celery = "==5.2.7" +certifi = "==2022.12.7" +cffi = "==1.15.1" +charset-normalizer = "==2.0.12" +click = "==8.1.3" +click-didyoumean = "==0.3.0" +click-plugins = "==1.1.1" +click-repl = "==0.2.0" +cryptography = "==39.0.1" +cssselect = "==1.2.0" +cssselect2 = "==0.7.0" +cssutils = "==2.6.0" +defusedxml = "==0.7.1" +diff-match-patch = "==20121119" +django = "==3.2.19" +django-ipware = "==1.1.6" +django-jinja = "==2.10.2" +django-pglocks = "==1.0.4" +django-picklefield = "==3.1" +django-sampledatahelper = "==0.4.1" +django-sites = "==0.11" +django-sr = "==0.0.4" +djmail = "==2.0.0" +django-storages = "*" +boto3 = "==1.27.0" +docopt = "==0.6.2" +easy-thumbnails = "==2.8.5" +gunicorn = "==20.1.0" +html5lib = "==1.1" +idna = "==3.4" +imageio = "==2.25.1" +importlib-metadata = "==6.0.0" +jinja2 = "==3.1.2" +kombu = "==5.2.4" +lxml = "==4.9.2" +markdown = "==3.4.1" +markupsafe = "==2.1.2" +monotonic = "==1.6" +netaddr = "==0.8.0" +networkx = "==3.0" +numpy = "==1.24.2" +oauthlib = {version = "==3.2.2", extras = ["signedtoken"]} +pillow = "==9.4.0" +premailer = "==3.0.1" +prompt-toolkit = "==3.0.36" +psd-tools = "==1.9.18" +psycopg2 = "==2.9.5" +pycparser = "==2.21" +pygments = "==2.14.0" +pyjwt = "==2.6.0" +pymdown-extensions = "==9.9.2" +python-dateutil = "==2.7.5" +python-magic = "==0.4.15" +pytz = "==2022.7.1" +pywavelets = "==1.4.1" +raven = "==6.10.0" +redis = "==4.5.3" +requests = "==2.27.1" +requests-oauthlib = "==1.3.1" +rudder-sdk-python = "==1.0.0b1" +sampledata = "==0.3.7" +scikit-image = "==0.19.3" +scipy = "==1.10.0" +serpy = "==0.1.1" +six = "==1.16.0" +sqlparse = "==0.4.3" +tifffile = "==2023.2.3" +tinycss2 = "==1.2.1" +unidecode = "==0.4.20" +urllib3 = "==1.26.14" +vine = "==5.0.0" +wcwidth = "==0.2.6" +webcolors = "==1.9.1" +webencodings = "==0.5.1" +zipp = "==1.2.0" + +[dev-packages] + +[requires] +python_version = "3.8" diff --git a/docker/config.py b/docker/config.py index 41d1ebf7..5a4e9407 100644 --- a/docker/config.py +++ b/docker/config.py @@ -50,6 +50,9 @@ ######################################### MEDIA_URL = f"{ TAIGA_URL }/media/" DEFAULT_FILE_STORAGE = "taiga_contrib_protected.storage.ProtectedFileSystemStorage" +AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME") +if AWS_STORAGE_BUCKET_NAME: + DEFAULT_FILE_STORAGE = "settings.storage_backend.PrivateMediaStorage" THUMBNAIL_DEFAULT_STORAGE = DEFAULT_FILE_STORAGE STATIC_URL = f"{ TAIGA_URL }/static/" diff --git a/requirements.in b/requirements.in index acd10b34..617282d0 100644 --- a/requirements.in +++ b/requirements.in @@ -11,6 +11,7 @@ django-sites==0.11 django-sr==0.0.4 djmail==2.0.0 easy-thumbnails==2.8.5 +django-storages==1.13.2 gunicorn==20.1.0 netaddr<0.9 premailer==3.0.1 diff --git a/requirements.txt b/requirements.txt index d0c2db70..c5ca77c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -71,20 +71,22 @@ django==3.2.19 # easy-thumbnails django-ipware==1.1.6 # via -r requirements.in -django-jinja==2.10.2 +django-jinja # via -r requirements.in django-pglocks==1.0.4 # via -r requirements.in django-picklefield==3.1 # via -r requirements.in -django-sampledatahelper==0.4.1 +django-sampledatahelper # via -r requirements.in django-sites==0.11 # via -r requirements.in -django-sr==0.0.4 +django-sr # via -r requirements.in djmail==2.0.0 # via -r requirements.in +django-storages +boto3==1.27.0 docopt==0.6.2 # via psd-tools easy-thumbnails==2.8.5 @@ -93,7 +95,7 @@ gunicorn==20.1.0 # via -r requirements.in html5lib==1.1 # via -r requirements.in -idna==3.4 +idna # via requests imageio==2.25.1 # via scikit-image @@ -175,7 +177,7 @@ raven==6.10.0 # via -r requirements.in redis==4.5.3 # via -r requirements.in -requests==2.27.1 +requests # via # -r requirements.in # asana @@ -220,7 +222,7 @@ tinycss2==1.2.1 # cssselect2 unidecode==0.4.20 # via -r requirements.in -urllib3==1.26.14 +urllib3 # via requests vine==5.0.0 # via diff --git a/settings/common.py b/settings/common.py index 6fa19c2b..0feae968 100644 --- a/settings/common.py +++ b/settings/common.py @@ -683,3 +683,25 @@ print("Try: \033[1;33mpy.test\033[0m") sys.exit(0) # NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE + + +AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME") +if AWS_STORAGE_BUCKET_NAME: + del STATIC_ROOT + AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID") + AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY") + + AWS_S3_CUSTOM_DOMAIN = f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com" + AWS_S3_OBJECT_PARAMETERS = { + "CacheControl": "max-age=86400", + } + AWS_LOCATION = "static" + AWS_S3_SIGNATURE_VERSION = "s3v4" + + STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), + ] + + STATIC_URL = f"https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_LOCATION}/" + STATICFILES_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" + DEFAULT_FILE_STORAGE = "settings.storage_backend.PrivateMediaStorage" diff --git a/settings/storage_backend.py b/settings/storage_backend.py new file mode 100644 index 00000000..979a7058 --- /dev/null +++ b/settings/storage_backend.py @@ -0,0 +1,13 @@ +from storages.backends.s3boto3 import S3Boto3Storage + + +class MediaStorage(S3Boto3Storage): + location = 'media' + file_overwrite = True + + +class PrivateMediaStorage(S3Boto3Storage): + location = "private" + default_acl = "private" + file_overwrite = False + custom_domain = False \ No newline at end of file