diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2c1b587 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +media +data +env +static +.git +.gitignore +*.log +~* +*.pyc +.cache +.project +.vscode +.travis.yml +.env +.idea \ No newline at end of file diff --git a/bin/run_sigi b/bin/run_sigi index 3f571d8..105b130 100755 --- a/bin/run_sigi +++ b/bin/run_sigi @@ -1,28 +1,26 @@ #!/bin/bash +# This script expects the following ENV variables: +# $SOCKS : Path to the dir that holds unix socket file +# - Defined as /srv/interlegis/socks in Dockerfile +# $HOME : Path to app home work dir. +# - Defined as /srv/interlegis/sigi in Dockerfile + NAME="sigi" -DJANGODIR=/srv/sigi -VENVDIR=/srv/.virtualenvs/sigi/bin -SOCKFILE=/var/run/sigi/sigi.sock -USER=sigi -GROUP=sigi +SOCKFILE=$SOCKS/sigi.sock NUM_WORKERS=3 # = 2 * CPUs + 1 DJANGO_SETTINGS_MODULE=sigi.settings DJANGO_WSGI_MODULE=sigi.wsgi echo "Starting $NAME as `whoami`" -cd $DJANGODIR +cd ${HOME} export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE -export PYTHONPATH=$DJANGODIR:$PYTHONPATH - -RUNDIR=$(dirname $SOCKFILE) -test -d $RUNDIR || mkdir -p $RUNDIR +export PYTHONPATH=${HOME}:$PYTHONPATH -exec ${VENVDIR}/gunicorn ${DJANGO_WSGI_MODULE}:application \ +exec gunicorn ${DJANGO_WSGI_MODULE}:application \ --name $NAME \ --workers $NUM_WORKERS \ ---user=$USER --group=$GROUP \ --log-level=debug \ --timeout=180 \ --graceful-timeout=180 \ diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..f089fa1 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,100 @@ +FROM python:3.8-slim-buster + +# Setup env +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV DEBIAN_FRONTEND=noninteractive + +# App env +ENV INTERLEGIS=/srv/interlegis +ENV HOME=${INTERLEGIS}/sigi +ENV SOCKS=${INTERLEGIS}/socks + +# Settings args - must be provided in the build process +ENV DEBUG=False +ENV ADMINS="(('SIGI Administrator', 'sigi@interlegis.leg.br'),)" +ENV EMAIL_PORT=25 +ENV EMAIL_HOST=mail.interlegis.leg.br +ENV EMAIL_HOST_USER='' +ENV EMAIL_HOST_PASSWORD='' +ENV EMAIL_SUBJECT_PREFIX='[SIGI]' +ENV EMAIL_USE_LOCALTIME=True +ENV EMAIL_USE_TLS=False +ENV EMAIL_USE_SSL=False +ENV EMAIL_TIMEOUT=120 +ENV DATABASE_URL='sqlite:////:memory:' +ENV AUTH_LDAP_SERVER_URI='' +ENV AUTH_LDAP_BIND_DN='' +ENV AUTH_LDAP_BIND_PASSWORD='' +ENV AUTH_LDAP_USER='' +ENV AUTH_LDAP_USER_SEARCH_STRING='' +ENV AUTH_LDAP_GROUP='' +ENV AUTH_LDAP_GROUP_SEARCH_STRING='' +ENV AUTH_LDAP_GROUP_TYPE_STRING='' +ENV AUTH_LDAP_USER_ATTR_MAP='' +ENV AUTH_LDAP_PROFILE_ATTR_MAP='' +ENV AUTH_LDAP_FIND_GROUP_PERMS='' +ENV AUTH_LDAP_MIRROR_GROUPS='' +ENV AUTH_LDAP_CACHE_GROUPS='' +ENV AUTH_LDAP_GROUP_CACHE_TIMEOUT='' +ENV AUTH_PROFILE_MODULE='' + +# Install env +ENV RUN_PACKAGES gcc locales build-essential python3-dev graphviz \ + libgraphviz-dev pkg-config libpq-dev postgresql-client \ + libsasl2-dev libldap2-dev libssl-dev vim nginx + +# Install required packages +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y ${RUN_PACKAGES} + +# Set encode language/charset +ENV DESIRED_LANG=pt_BR.UTF-8 +RUN sed -i -e "s/# ${DESIRED_LANG}.*/${DESIRED_LANG} UTF-8/" /etc/locale.gen && \ + locale-gen pt_BR.UTF-8 && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=${DESIRED_LANG} +ENV LANG=${DESIRED_LANG} + +# Create work and unix socket dirs +RUN mkdir -p ${HOME} && \ + mkdir ${SOCKS} && \ + mkdir ${HOME}/media && \ + mkdir ${HOME}/static + +# Add sigi to container +WORKDIR ${HOME} +ADD . ${HOME} + +# Install python packages +RUN pip install -qq --upgrade pip setuptools && \ + if [ $DEBUG = False ]; then \ + pip install -qq -r ${HOME}/requirements/requirements.txt; \ + else \ + pip install -qq -r ${HOME}/requirements/dev-requirements.txt; \ + fi + +# Generate a new secret key +RUN echo $'\n\n'SECRET_KEY=`python manage.py generate_secret_key` >> ${HOME}/sigi/.env + +# Prepare run script to start application server +RUN chmod +x ${HOME}/bin/run_sigi + +# Data migration +RUN python manage.py migrate + +# Static files +RUN python manage.py collectstatic --noinput --clear + +# nginx setup +RUN rm -f /etc/nginx/sites-enabled/* +RUN ln -s ${HOME}/etc/nginx/sites-available/sigi.vhost /etc/nginx/sites-enabled/sigi.vhost +RUN mkdir -p /var/log/sigi + +EXPOSE 80/tcp 443/tcp + +ENV DEBIAN_FRONTEND=teletype + +VOLUME ["/srv/interlegis/sigi/media"] +CMD service nginx start && /srv/interlegis/sigi/bin/run_sigi \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..bdc66ee --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,34 @@ +version: "3.3" + +services: + sigidb: + image: postgres:alpine3.15 + volumes: + - ../data/db:/var/lib/postgresql/data + environment: + - LANG=pt_BR.UTF-8 + - POSTGRES_NAME=sigi + - POSTGRES_USER=sigi + - POSTGRES_PASSWORD=sigi + - PGDATA=/var/lib/postgresql/data + networks: + - sigi-net + sigi: + build: + context: ${PWD}/ + dockerfile: docker/Dockerfile + ports: + - "8000:80" + volumes: + - ../data/media:/srv/interlegis/sigi/media + environment: + - LANG=pt_BR.UTF-8 + - DATABASE_URL=psql://sigi:sigi@sigidb:5432/sigi + depends_on: + - sigidb + networks: + - sigi-net +networks: + sigi-net: + name: sigi-net + driver: bridge \ No newline at end of file diff --git a/etc/nginx/sites-available/sigi.vhost b/etc/nginx/sites-available/sigi.vhost index e9bfc37..68df513 100644 --- a/etc/nginx/sites-available/sigi.vhost +++ b/etc/nginx/sites-available/sigi.vhost @@ -1,5 +1,5 @@ upstream sigi_app_server { - server unix:/var/run/sigi/sigi.sock fail_timeout=0; + server unix:/srv/interlegis/socks/sigi.sock fail_timeout=0; } server { @@ -12,11 +12,11 @@ server { error_log /var/log/sigi/sigi-error.log; location /static/ { - alias /srv/sigi/static/; + alias /srv/interlegis/sigi/static/; } location /media/ { - alias /srv/sigi/media/; + alias /srv/interlegis/sigi/media/; } location / { diff --git a/requirements/dev-requirements.txt b/requirements/dev-requirements.txt index c9ac018..1da8f20 100644 --- a/requirements/dev-requirements.txt +++ b/requirements/dev-requirements.txt @@ -1,3 +1,3 @@ -r requirements.txt django-debug-toolbar==3.2.4 -pygraphviz==1.7 +pygraphviz==1.9 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index de2b47f..7fabe19 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,11 +1,14 @@ +gunicorn==20.1.0 ipython==8.2.0 Pillow==9.1.0 -psycopg2==2.9.3 +psycopg2-binary==2.9.3 requests==2.27.1 weasyprint==54.3 Django==4.0.3 +django-auth-ldap==4.0.0 +django-environ==0.8.1 django-extensions==3.1.5 -django-import-export==2.7.1 +django-import-export==2.8.0 django-localflavor==3.1 django-material-admin==1.8.6 django-tinymce==3.4.0 diff --git a/sigi/settings/menu_conf.yaml b/sigi/menu_conf.yaml similarity index 100% rename from sigi/settings/menu_conf.yaml rename to sigi/menu_conf.yaml diff --git a/sigi/settings/base.py b/sigi/settings.py similarity index 55% rename from sigi/settings/base.py rename to sigi/settings.py index e5e6457..868a034 100644 --- a/sigi/settings/base.py +++ b/sigi/settings.py @@ -10,12 +10,27 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.0/ref/settings/ """ +import environ from pathlib import Path from django.utils.translation import gettext_lazy as _ # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent +BASE_DIR = Path(__file__).resolve().parent +env = environ.Env() +env.read_env(BASE_DIR / ".env") + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = env('SECRET_KEY', default="Unsafe") + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = env('DEBUG', default=False, cast=bool) + +ALLOWED_HOSTS = ['*'] + +INTERNAL_IPS = ["127.0.0.1",] + +ADMINS = env('ADMINS', eval) # Application definition @@ -36,7 +51,6 @@ INSTALLED_APPS = [ 'django.forms', 'material', 'material.admin', - # 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -55,6 +69,28 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +if DEBUG: + INSTALLED_APPS = ['debug_toolbar',] + INSTALLED_APPS + MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware',] + \ + MIDDLEWARE + +EMAIL_PORT=env("EMAIL_PORT", int, default=25) +EMAIL_HOST=env("EMAIL_HOST", default="") +EMAIL_HOST_USER=env("EMAIL_HOST_USER", default="") +EMAIL_HOST_PASSWORD=env("EMAIL_HOST_PASSWORD", default="") +EMAIL_SUBJECT_PREFIX=env("EMAIL_SUBJECT_PREFIX", default="[SIGI]") +EMAIL_USE_LOCALTIME=env("EMAIL_USE_LOCALTIME", bool, default=False) +EMAIL_USE_TLS=env("EMAIL_USE_TLS", bool, default=False) +EMAIL_USE_SSL=env("EMAIL_USE_SSL", bool, default=False) +EMAIL_TIMEOUT=env("EMAIL_TIMEOUT", int, default=None) + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + 'default': env.db(), +} + ROOT_URLCONF = 'sigi.urls' TEMPLATES = [ @@ -94,13 +130,63 @@ USE_TZ = True USE_THOUSAND_SEPARATOR = True +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +if env('AUTH_LDAP_SERVER_URI', default=None): + AUTHENTICATION_BACKENDS = [ + "django_auth_ldap.backend.LDAPBackend", + "django.contrib.auth.backends.ModelBackend", + ] + from django_auth_ldap.config import LDAPSearch, GroupOfNamesType + import ldap + AUTH_LDAP_SERVER_URI = env('AUTH_LDAP_SERVER_URI') + AUTH_LDAP_BIND_DN = env('AUTH_LDAP_BIND_DN') + AUTH_LDAP_BIND_PASSWORD = env('AUTH_LDAP_BIND_PASSWORD') + AUTH_LDAP_USER = env('AUTH_LDAP_USER') + AUTH_LDAP_USER_SEARCH = LDAPSearch( + AUTH_LDAP_USER, + ldap.SCOPE_SUBTREE, + env('AUTH_LDAP_USER_SEARCH_STRING') + ) + AUTH_LDAP_GROUP = env('AUTH_LDAP_GROUP') + AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + AUTH_LDAP_GROUP, + ldap.SCOPE_SUBTREE, + env('AUTH_LDAP_GROUP_SEARCH_STRING') + ) + AUTH_LDAP_GROUP_TYPE = GroupOfNamesType( + name_attr=env('AUTH_LDAP_GROUP_TYPE_STRING') + ) + AUTH_LDAP_USER_ATTR_MAP = env('AUTH_LDAP_USER_ATTR_MAP', cast=eval) + AUTH_LDAP_PROFILE_ATTR_MAP = env('AUTH_LDAP_PROFILE_ATTR_MAP', eval) + AUTH_LDAP_FIND_GROUP_PERMS = env('AUTH_LDAP_FIND_GROUP_PERMS', bool) + AUTH_LDAP_MIRROR_GROUPS = env('AUTH_LDAP_MIRROR_GROUPS', bool) + AUTH_LDAP_CACHE_GROUPS = env('AUTH_LDAP_CACHE_GROUPS', bool) + AUTH_LDAP_GROUP_CACHE_TIMEOUT = env('AUTH_LDAP_GROUP_CACHE_TIMEOUT', int) + AUTH_PROFILE_MODULE = env('AUTH_PROFILE_MODULE') # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ STATIC_URL = 'static/' STATICFILES_DIRS = [BASE_DIR / "static",] -STATIC_ROOT = '/var/www/sigi/static/' +STATIC_ROOT = BASE_DIR / '../static/' # Media files # https://docs.djangoproject.com/en/4.0/topics/files/#managing-files @@ -126,8 +212,6 @@ MATERIAL_ADMIN_SITE = { 'HEADER': _('SIGI - Sistema de Informações do Interlegis'), 'TITLE': _('SIGI'), 'FAVICON': 'img/favicon.ico', - # 'MAIN_BG_COLOR': 'color', # Admin site main color, css color should be specified - # 'MAIN_HOVER_COLOR': 'color', # Admin site main hover color, css color should be specified 'PROFILE_PICTURE': 'img/interlegis.jpeg', # Admin site profile picture (path to static should be specified) 'PROFILE_BG': 'img/engitec.jpeg', # Admin site profile background (path to static should be specified) 'LOGIN_LOGO': 'img/interlegis.jpeg', # Admin site logo on login page (path to static should be specified) @@ -135,15 +219,8 @@ MATERIAL_ADMIN_SITE = { 'SHOW_THEMES': False, # Show default admin themes button 'TRAY_REVERSE': False, # Hide object-tools and additional-submit-line by default 'NAVBAR_REVERSE': False, # Hide side navbar by default - # 'SHOW_COUNTS': True, # Show instances counts for each model - # 'APP_ICONS': { # Set icons for applications(lowercase), including 3rd party apps, {'application_name': 'material_icon_name', ...} - # 'sites': 'send', - # }, - # 'MODEL_ICONS': { # Set icons for models(lowercase), including 3rd party models, {'model_name': 'material_icon_name', ...} - # 'site': 'contact_mail', - # } } # SIGI specific settings -MENU_FILE = BASE_DIR / 'settings/menu_conf.yaml' \ No newline at end of file +MENU_FILE = BASE_DIR / 'menu_conf.yaml' diff --git a/sigi/settings/__init__.py b/sigi/settings/__init__.py deleted file mode 100644 index 537d0c8..0000000 --- a/sigi/settings/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -try: - from sigi.settings.production import * -except ImportError: - from django.core.exceptions import ImproperlyConfigured - msg = """ - ###################################################################### - Arquivo production.py (django settings) nao encontrado. - Se vc esta num ambiente de desenvolvimento pode cria-lo com - ln -s development.py production.py - ###################################################################### -""" - raise ImproperlyConfigured(msg) diff --git a/sigi/settings/development.py b/sigi/settings/development.py deleted file mode 100644 index f538605..0000000 --- a/sigi/settings/development.py +++ /dev/null @@ -1,57 +0,0 @@ -from sigi.settings.base import * - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-z%cw$-jyh13g2an=8r5ltkyub75bfc$34o(k#%jncdcdgr2$3#' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - -INTERNAL_IPS = ["127.0.0.1",] - -# Application definition - -INSTALLED_APPS = ['debug_toolbar',] + INSTALLED_APPS - -MIDDLEWARE = ['debug_toolbar.middleware.DebugToolbarMiddleware',] + MIDDLEWARE - -# Database -# https://docs.djangoproject.com/en/4.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'sigi', - 'USER': 'sigi', - 'PASSWORD': '123456', - 'HOST': 'localhost', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.0/howto/static-files/ - -STATIC_ROOT = BASE_DIR / '../static/' \ No newline at end of file