commit
350d93f820
|
@ -0,0 +1,5 @@
|
||||||
|
SECRET_KEY=k_=*vfue(^campsl63)7w5m&cu9u4o4-!vaw94qzyrymyv0hgg
|
||||||
|
DEBUG=True
|
||||||
|
ALLOWED_HOSTS=.localhost,127.0.0.1
|
||||||
|
DATABASE_URL=postgres://USER:PASSWORD@HOST:PORT/NAME
|
||||||
|
STATIC_ROOT=PATH_TO_STATIC_ROOT
|
|
@ -11,7 +11,7 @@ If you are planing to do some development you may want to consider doing it unde
|
||||||
|
|
||||||
2. Build a new image, create and start a container
|
2. Build a new image, create and start a container
|
||||||
```bash
|
```bash
|
||||||
curl -L http://git.io/orchestra-Dockerfile > /tmp/Dockerfile
|
curl -L https://raw.githubusercontent.com/ribaguifi/django-orchestra/master/scripts/containers/Dockerfile > /tmp/Dockerfile
|
||||||
docker build -t orchestra /tmp/
|
docker build -t orchestra /tmp/
|
||||||
docker create --name orchestra -i -t -u orchestra -w /home/orchestra orchestra bash
|
docker create --name orchestra -i -t -u orchestra -w /home/orchestra orchestra bash
|
||||||
docker start orchestra
|
docker start orchestra
|
||||||
|
@ -21,12 +21,13 @@ If you are planing to do some development you may want to consider doing it unde
|
||||||
|
|
||||||
3. Deploy django-orchestra development environment, inside the container
|
3. Deploy django-orchestra development environment, inside the container
|
||||||
```bash
|
```bash
|
||||||
bash <( curl -L http://git.io/orchestra-deploy ) --dev
|
bash <( curl -L https://raw.githubusercontent.com/ribaguifi/django-orchestra/master/scripts/containers/deploy.sh ) --dev
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Nginx should be serving on port 80, but Django's development server can be used as well:
|
3. Nginx should be serving on port 80, but Django's development server can be used as well:
|
||||||
```bash
|
```bash
|
||||||
cd panel
|
cd panel
|
||||||
|
python3 manage.py migrate
|
||||||
python3 manage.py runserver 0.0.0.0:8888
|
python3 manage.py runserver 0.0.0.0:8888
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -34,5 +35,5 @@ If you are planing to do some development you may want to consider doing it unde
|
||||||
5. To upgrade to current master just re-run the deploy script
|
5. To upgrade to current master just re-run the deploy script
|
||||||
```bash
|
```bash
|
||||||
git pull origin master
|
git pull origin master
|
||||||
bash <( curl -L http://git.io/orchestra-deploy ) --dev
|
bash <( curl -L https://raw.githubusercontent.com/ribaguifi/django-orchestra/master/scripts/containers/deploy.sh ) --dev
|
||||||
```
|
```
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
We need have python3.6
|
||||||
|
|
||||||
|
#Install Packages
|
||||||
|
```bash
|
||||||
|
apt=(
|
||||||
|
bind9utils
|
||||||
|
ca-certificates
|
||||||
|
gettext
|
||||||
|
libcrack2-dev
|
||||||
|
libxml2-dev
|
||||||
|
libxslt1-dev
|
||||||
|
ssh-client
|
||||||
|
wget
|
||||||
|
xvfb
|
||||||
|
zlib1g-dev
|
||||||
|
git
|
||||||
|
iceweasel
|
||||||
|
dnsutils
|
||||||
|
postgresql-contrib
|
||||||
|
)
|
||||||
|
sudo apt-get install --no-install-recommends -y ${apt[@]}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is necessary install *wkhtmltopdf*
|
||||||
|
You can install it from https://wkhtmltopdf.org/downloads.html
|
||||||
|
|
||||||
|
Clone this repository
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/ribaguifi/django-orchestra
|
||||||
|
```
|
||||||
|
|
||||||
|
Prepare env and install requirements
|
||||||
|
```bash
|
||||||
|
cd django-orchestra
|
||||||
|
python3.6 -m venv env
|
||||||
|
source env/bin/activate
|
||||||
|
pip3 install --upgrade pip
|
||||||
|
pip3 install -r total_requirements.txt
|
||||||
|
pip3 install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure project using environment file (you can use provided example as quickstart):
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Prepare your Postgres database (create database, user and grant permissions):
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE myproject;
|
||||||
|
CREATE USER myuser WITH PASSWORD 'password';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE myproject TO myuser;
|
||||||
|
```
|
||||||
|
|
||||||
|
Prepare a new project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
django-admin.py startproject PROJECT_NAME --template="orchestra/conf/ribaguifi_template"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run migrations:
|
||||||
|
```bash
|
||||||
|
python3 manage.py migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
(Optional) You can start a Django development server to check that everything is ok.
|
||||||
|
```bash
|
||||||
|
python3 manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your browser.
|
|
@ -0,0 +1,132 @@
|
||||||
|
# System requirements:
|
||||||
|
The most important requirement is use python3.6
|
||||||
|
we need install this packages:
|
||||||
|
```
|
||||||
|
bind9utils
|
||||||
|
ca-certificates
|
||||||
|
gettext
|
||||||
|
libcrack2-dev
|
||||||
|
libxml2-dev
|
||||||
|
libxslt1-dev
|
||||||
|
python3
|
||||||
|
python3-pip
|
||||||
|
python3-dev
|
||||||
|
ssh-client
|
||||||
|
wget
|
||||||
|
xvfb
|
||||||
|
zlib1g-dev
|
||||||
|
git
|
||||||
|
iceweasel
|
||||||
|
dnsutils
|
||||||
|
```
|
||||||
|
We need install too a *wkhtmltopdf* package
|
||||||
|
You can use one of your OS or get it from original.
|
||||||
|
This it is in https://wkhtmltopdf.org/downloads.html
|
||||||
|
|
||||||
|
# pip installations
|
||||||
|
We need install this packages:
|
||||||
|
```
|
||||||
|
Django==1.10.5
|
||||||
|
django-fluent-dashboard==0.6.1
|
||||||
|
django-admin-tools==0.8.0
|
||||||
|
django-extensions==1.7.4
|
||||||
|
django-celery==3.1.17
|
||||||
|
celery==3.1.23
|
||||||
|
kombu==3.0.35
|
||||||
|
billiard==3.3.0.23
|
||||||
|
Markdown==2.4
|
||||||
|
djangorestframework==3.4.7
|
||||||
|
ecdsa==0.11
|
||||||
|
Pygments==1.6
|
||||||
|
django-filter==0.15.2
|
||||||
|
jsonfield==0.9.22
|
||||||
|
python-dateutil==2.2
|
||||||
|
https://github.com/glic3rinu/passlib/archive/master.zip
|
||||||
|
django-iban==0.3.0
|
||||||
|
requests
|
||||||
|
phonenumbers
|
||||||
|
django-countries
|
||||||
|
django-localflavor
|
||||||
|
amqp
|
||||||
|
anyjson
|
||||||
|
pytz
|
||||||
|
cracklib
|
||||||
|
lxml==3.3.5
|
||||||
|
selenium
|
||||||
|
xvfbwrapper
|
||||||
|
freezegun
|
||||||
|
coverage
|
||||||
|
flake8
|
||||||
|
django-debug-toolbar==1.3.0
|
||||||
|
django-nose==1.4.4
|
||||||
|
sqlparse
|
||||||
|
pyinotify
|
||||||
|
PyMySQL
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use Orchestra you need to install from pip like this:
|
||||||
|
```
|
||||||
|
pip3 install http://git.io/django-orchestra-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
But if you want develop orquestra you need to do this:
|
||||||
|
```
|
||||||
|
git clone https://github.com/ribaguifi/django-orchestra
|
||||||
|
pip install -e django-orchestra
|
||||||
|
```
|
||||||
|
|
||||||
|
# Database
|
||||||
|
For default use sqlite3 if you want to use postgresql you need install this packages:
|
||||||
|
|
||||||
|
```
|
||||||
|
psycopg2 postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use it for debian or ubuntu:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install python3-psycopg2 postgresql-contrib
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember create a database for your project and give permitions for the correct user like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
psql -U postgres
|
||||||
|
psql (12.4)
|
||||||
|
Digite «help».
|
||||||
|
|
||||||
|
postgres=# CREATE database orchesta;
|
||||||
|
postgres=# CREATE USER orchesta WITH PASSWORD 'orquesta';
|
||||||
|
postgres=# GRANT ALL PRIVILEGES ON DATABASE orchesta TO orchesta;
|
||||||
|
```
|
||||||
|
|
||||||
|
# Create new project
|
||||||
|
You can use orchestra-admin for create your new project
|
||||||
|
```
|
||||||
|
orchestra-admin startproject <project_name> # e.g. panel
|
||||||
|
cd <project_name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Next we need change the settings.py for configure the correct database
|
||||||
|
|
||||||
|
In settings.py we need change the DATABASE section like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': 'orchestra'
|
||||||
|
'USER': 'orchestra',
|
||||||
|
'PASSWORD': 'orchestra',
|
||||||
|
'HOST': 'localhost',
|
||||||
|
'PORT': '5432',
|
||||||
|
'CONN_MAX_AGE': 60*10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For end you need to do the migrations:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 manage.py migrate
|
||||||
|
```
|
|
@ -174,7 +174,7 @@ function install_requirements () {
|
||||||
minor=$(echo -e "$wkhtmltox_version\n0.12.2.1" | sort -V | head -n 1)
|
minor=$(echo -e "$wkhtmltox_version\n0.12.2.1" | sort -V | head -n 1)
|
||||||
if [[ ! $wkhtmltox_version ]] || [[ $wkhtmltox_version != 0.12.2.1 && $minor == ${wkhtmltox_version} ]]; then
|
if [[ ! $wkhtmltox_version ]] || [[ $wkhtmltox_version != 0.12.2.1 && $minor == ${wkhtmltox_version} ]]; then
|
||||||
wkhtmltox=$(mktemp)
|
wkhtmltox=$(mktemp)
|
||||||
wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-jessie-amd64.deb -O ${wkhtmltox}
|
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb -O ${wkhtmltox}
|
||||||
dpkg -i ${wkhtmltox} || { echo "Installing missing dependencies for wkhtmltox..." && apt-get -f -y install; }
|
dpkg -i ${wkhtmltox} || { echo "Installing missing dependencies for wkhtmltox..." && apt-get -f -y install; }
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if sys.version_info < (3, 3):
|
||||||
|
cmd = ' '.join(sys.argv)
|
||||||
|
sys.stderr.write("Sorry, Orchestra requires at least Python 3.3, try with:\n$ python3 %s\n" % cmd)
|
||||||
|
sys.exit(1)
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
execute_from_command_line(sys.argv)
|
|
@ -0,0 +1,257 @@
|
||||||
|
"""
|
||||||
|
Django settings for {{ project_name }} project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django {{ django_version }}.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/{{ docs_version }}/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from decouple import config, Csv
|
||||||
|
from dj_database_url import parse as db_url
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = '{{ secret_key }}'
|
||||||
|
# SECRET_KEY = config('SECRET_KEY')
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = config('DEBUG', default=False, cast=bool)
|
||||||
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
# django-orchestra apps
|
||||||
|
'orchestra',
|
||||||
|
'orchestra.contrib.accounts',
|
||||||
|
'orchestra.contrib.systemusers',
|
||||||
|
'orchestra.contrib.contacts',
|
||||||
|
'orchestra.contrib.orchestration',
|
||||||
|
'orchestra.contrib.bills',
|
||||||
|
'orchestra.contrib.payments',
|
||||||
|
'orchestra.contrib.tasks',
|
||||||
|
'orchestra.contrib.mailer',
|
||||||
|
'orchestra.contrib.history',
|
||||||
|
'orchestra.contrib.issues',
|
||||||
|
'orchestra.contrib.services',
|
||||||
|
'orchestra.contrib.plans',
|
||||||
|
'orchestra.contrib.orders',
|
||||||
|
'orchestra.contrib.domains',
|
||||||
|
'orchestra.contrib.mailboxes',
|
||||||
|
'orchestra.contrib.lists',
|
||||||
|
'orchestra.contrib.webapps',
|
||||||
|
'orchestra.contrib.websites',
|
||||||
|
'orchestra.contrib.letsencrypt',
|
||||||
|
'orchestra.contrib.databases',
|
||||||
|
'orchestra.contrib.vps',
|
||||||
|
'orchestra.contrib.saas',
|
||||||
|
'orchestra.contrib.miscellaneous',
|
||||||
|
|
||||||
|
# Third-party apps
|
||||||
|
'django_extensions',
|
||||||
|
'djcelery',
|
||||||
|
'fluent_dashboard',
|
||||||
|
'admin_tools',
|
||||||
|
'admin_tools.theming',
|
||||||
|
'admin_tools.menu',
|
||||||
|
'admin_tools.dashboard',
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
'passlib.ext.django',
|
||||||
|
'django_countries',
|
||||||
|
# 'debug_toolbar',
|
||||||
|
|
||||||
|
# Django.contrib
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.admin.apps.SimpleAdminConfig',
|
||||||
|
|
||||||
|
# Last to load
|
||||||
|
'orchestra.contrib.resources',
|
||||||
|
'orchestra.contrib.settings',
|
||||||
|
# 'django_nose',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_URLCONF = '{{ project_name }}.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'orchestra.core.context_processors.site',
|
||||||
|
],
|
||||||
|
'loaders': [
|
||||||
|
'admin_tools.template_loaders.Loader',
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': config(
|
||||||
|
'DATABASE_URL',
|
||||||
|
default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
cast=db_url
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
TIME_ZONE = open('/etc/timezone', 'r').read().strip()
|
||||||
|
except IOError:
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
|
||||||
|
|
||||||
|
# Absolute path to the directory static files should be collected to.
|
||||||
|
# Don't put anything in this directory yourself; store your static files
|
||||||
|
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
|
||||||
|
# Example: "/home/media/media.lawrence.com/static/"
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
|
|
||||||
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
|
||||||
|
|
||||||
|
# Path used for database translations files
|
||||||
|
LOCALE_PATHS = (
|
||||||
|
os.path.join(BASE_DIR, 'locale'),
|
||||||
|
)
|
||||||
|
|
||||||
|
ORCHESTRA_SITE_NAME = '{{ project_name }}'
|
||||||
|
|
||||||
|
|
||||||
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
|
# 'django.middleware.locale.LocaleMiddleware'
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'orchestra.core.caches.RequestCacheMiddleware',
|
||||||
|
# also handles transations, ATOMIC_REQUESTS does not wrap middlewares
|
||||||
|
'orchestra.contrib.orchestration.middlewares.OperationsMiddleware',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'accounts.Account'
|
||||||
|
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'orchestra.permissions.auth.OrchestraPermissionBackend',
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'orchestra.contrib.mailer.backends.EmailBackend'
|
||||||
|
|
||||||
|
|
||||||
|
# Needed for Bulk operations
|
||||||
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = None
|
||||||
|
|
||||||
|
|
||||||
|
#################################
|
||||||
|
## 3RD PARTY APPS CONIGURATION ##
|
||||||
|
#################################
|
||||||
|
|
||||||
|
# Admin Tools
|
||||||
|
ADMIN_TOOLS_MENU = 'orchestra.admin.menu.OrchestraMenu'
|
||||||
|
|
||||||
|
# Fluent dashboard
|
||||||
|
ADMIN_TOOLS_INDEX_DASHBOARD = 'orchestra.admin.dashboard.OrchestraIndexDashboard'
|
||||||
|
FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons'
|
||||||
|
|
||||||
|
|
||||||
|
# Django-celery
|
||||||
|
import djcelery
|
||||||
|
djcelery.setup_loader()
|
||||||
|
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
|
||||||
|
|
||||||
|
|
||||||
|
# rest_framework
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'orchestra.permissions.api.OrchestraPermissionBackend',
|
||||||
|
),
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
),
|
||||||
|
'DEFAULT_FILTER_BACKENDS': (
|
||||||
|
('rest_framework.filters.DjangoFilterBackend',)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Use a UNIX compatible hash
|
||||||
|
PASSLIB_CONFIG = (
|
||||||
|
"[passlib]\n"
|
||||||
|
"schemes = sha512_crypt, django_pbkdf2_sha256, django_pbkdf2_sha1, "
|
||||||
|
" django_bcrypt, django_bcrypt_sha256, django_salted_sha1, des_crypt, "
|
||||||
|
" django_salted_md5, django_des_crypt, hex_md5, bcrypt, phpass\n"
|
||||||
|
"default = sha512_crypt\n"
|
||||||
|
"deprecated = django_pbkdf2_sha1, django_salted_sha1, django_salted_md5, "
|
||||||
|
" django_des_crypt, des_crypt, hex_md5\n"
|
||||||
|
"all__vary_rounds = 0.05\n"
|
||||||
|
"django_pbkdf2_sha256__min_rounds = 10000\n"
|
||||||
|
"sha512_crypt__min_rounds = 80000\n"
|
||||||
|
"staff__django_pbkdf2_sha256__default_rounds = 12500\n"
|
||||||
|
"staff__sha512_crypt__default_rounds = 100000\n"
|
||||||
|
"superuser__django_pbkdf2_sha256__default_rounds = 15000\n"
|
||||||
|
"superuser__sha512_crypt__default_rounds = 120000\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SHELL_PLUS_PRE_IMPORTS = (
|
||||||
|
('orchestra.contrib.orchestration.managers', ('orchestrate',)),
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'', include('orchestra.urls')),
|
||||||
|
]
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""
|
||||||
|
WSGI config for {{ project_name }} project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
|
@ -1,6 +1,5 @@
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
@ -19,26 +18,44 @@ def deprecate_periodic_tasks(names):
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
|
||||||
self.option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--no-restart', action='store_false', dest='restart', default=True,
|
|
||||||
help='Only install local requirements'),
|
|
||||||
make_option('--specifics', action='store_true', dest='specifics_only',
|
|
||||||
default=False, help='Only run version specific operations'),
|
|
||||||
make_option('--no-upgrade-notes', action='store_false', default=True,
|
|
||||||
dest='print_upgrade_notes', help='Do not print specific upgrade notes'),
|
|
||||||
make_option('--from', dest='version', default=False,
|
|
||||||
help="Orchestra's version from where you are upgrading, i.e 0.0.1"),
|
|
||||||
)
|
|
||||||
|
|
||||||
option_list = BaseCommand.option_list
|
|
||||||
help = 'Upgrades django-orchestra installation'
|
help = 'Upgrades django-orchestra installation'
|
||||||
# This command must be able to run in an environment with unsatisfied dependencies
|
# This command must be able to run in an environment with unsatisfied dependencies
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
can_import_settings = False
|
can_import_settings = False
|
||||||
requires_model_validation = False
|
requires_model_validation = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-restart',
|
||||||
|
action='store_false',
|
||||||
|
dest='restart',
|
||||||
|
default=True,
|
||||||
|
help='Only install local requirements'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--specifics',
|
||||||
|
action='store_true',
|
||||||
|
dest='specifics_only',
|
||||||
|
default=False,
|
||||||
|
help='Only run version specific operations'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-upgrade-notes',
|
||||||
|
action='store_false',
|
||||||
|
default=True,
|
||||||
|
dest='print_upgrade_notes',
|
||||||
|
help='Do not print specific upgrade notes'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--from',
|
||||||
|
dest='version',
|
||||||
|
default=False,
|
||||||
|
help="Orchestra's version from where you are upgrading, i.e 0.0.1"
|
||||||
|
)
|
||||||
|
|
||||||
@check_root
|
@check_root
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
version = options.get('version')
|
version = options.get('version')
|
||||||
|
|
|
@ -7,5 +7,4 @@ from orchestra.management.commands.startservices import ManageServiceCommand
|
||||||
class Command(ManageServiceCommand):
|
class Command(ManageServiceCommand):
|
||||||
services = settings.ORCHESTRA_RESTART_SERVICES
|
services = settings.ORCHESTRA_RESTART_SERVICES
|
||||||
action = 'restart'
|
action = 'restart'
|
||||||
option_list = BaseCommand.option_list
|
|
||||||
help = 'Restart all related services. Usefull for reload configuration and files.'
|
help = 'Restart all related services. Usefull for reload configuration and files.'
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import textwrap
|
import textwrap
|
||||||
from optparse import make_option
|
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
@ -10,21 +9,33 @@ from orchestra.utils.sys import run, check_root
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
help = 'Configure Celeryd to run with your orchestra instance.'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
self.option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--username', dest='username', default='orchestra',
|
|
||||||
help='Specifies the system user that would run celeryd.'),
|
|
||||||
make_option('--processes', dest='processes', default=2,
|
|
||||||
help='Number of celeryd processes.'),
|
|
||||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
|
||||||
help='Tells Django to NOT prompt the user for input of any kind. '
|
|
||||||
'You must use --username with --noinput, and must contain the '
|
|
||||||
'cleleryd process owner, which is the user how will perform tincd updates'),
|
|
||||||
)
|
|
||||||
|
|
||||||
option_list = BaseCommand.option_list
|
def add_arguments(self, parser):
|
||||||
help = 'Configure Celeryd to run with your orchestra instance.'
|
parser.add_argument(
|
||||||
|
'--username',
|
||||||
|
dest='username',
|
||||||
|
default='orchestra',
|
||||||
|
help='Specifies the system user that would run celeryd.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--processes',
|
||||||
|
dest='processes',
|
||||||
|
default=2,
|
||||||
|
help='Number of celeryd processes.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--noinput',
|
||||||
|
action='store_false',
|
||||||
|
dest='interactive',
|
||||||
|
default=True,
|
||||||
|
help='''Tells Django to NOT prompt the user for input of any kind.
|
||||||
|
You must use --username with --noinput, and must contain the
|
||||||
|
cleleryd process owner, which is the user how will perform tincd updates'''
|
||||||
|
)
|
||||||
|
|
||||||
@check_root
|
@check_root
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
from optparse import make_option
|
|
||||||
from os.path import expanduser
|
from os.path import expanduser
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -11,54 +10,117 @@ from orchestra.utils.sys import run, check_root, confirm
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
help = 'Configures nginx + uwsgi to run with your Orchestra instance.'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
self.option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--cert', dest='cert', default='',
|
|
||||||
help='Nginx SSL certificate, one will be created by default.'),
|
|
||||||
make_option('--cert-key', dest='cert_key', default='',
|
|
||||||
help='Nginx SSL certificate key.'),
|
|
||||||
|
|
||||||
make_option('--cert-path', dest='cert_path',
|
def add_arguments(self, parser):
|
||||||
default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.crt'),
|
parser.add_argument(
|
||||||
help='Nginx SSL certificate, one will be created by default.'),
|
'--cert',
|
||||||
make_option('--cert-key-path', dest='cert_key_path',
|
dest='cert',
|
||||||
default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.key'),
|
default='',
|
||||||
help='Nginx SSL certificate key.'),
|
help='Nginx SSL certificate, one will be created by default.',
|
||||||
# Cert options
|
)
|
||||||
make_option('--cert-override', dest='cert_override', action='store_true',
|
parser.add_argument(
|
||||||
default=False, help='Force override cert and keys if exists.'),
|
'--cert-key',
|
||||||
make_option('--cert-country', dest='cert_country', default='ES',
|
dest='cert_key',
|
||||||
help='Certificate Distinguished Name Country.'),
|
default='',
|
||||||
make_option('--cert-state', dest='cert_state', default='Spain',
|
help='Nginx SSL certificate key.'
|
||||||
help='Certificate Distinguished Name STATE.'),
|
)
|
||||||
make_option('--cert-locality', dest='cert_locality', default='Barcelona',
|
parser.add_argument(
|
||||||
help='Certificate Distinguished Name Country.'),
|
'--cert-path',
|
||||||
make_option('--cert-org_name', dest='cert_org_name', default='Orchestra',
|
dest='cert_path',
|
||||||
help='Certificate Distinguished Name Organization Name.'),
|
default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.crt'),
|
||||||
make_option('--cert-org_unit', dest='cert_org_unit', default='DevOps',
|
help='Nginx SSL certificate, one will be created by default.'
|
||||||
help='Certificate Distinguished Name Organization Unity.'),
|
)
|
||||||
make_option('--cert-email', dest='cert_email', default='orchestra@orchestra.lan',
|
parser.add_argument(
|
||||||
help='Certificate Distinguished Name Email Address.'),
|
'--cert-key-path',
|
||||||
make_option('--cert-common_name', dest='cert_common_name', default=None,
|
dest='cert_key_path',
|
||||||
help='Certificate Distinguished Name Common Name.'),
|
default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.key'),
|
||||||
|
help='Nginx SSL certificate key.'
|
||||||
make_option('--server-name', dest='server_name', default='',
|
)
|
||||||
help='Nginx SSL certificate key.'),
|
parser.add_argument(
|
||||||
make_option('--user', dest='user', default='',
|
'--cert-override',
|
||||||
help='uWSGI daemon user.'),
|
dest='cert_override',
|
||||||
make_option('--group', dest='group', default='',
|
action='store_true',
|
||||||
help='uWSGI daemon group.'),
|
default=False, help='Force override cert and keys if exists.'
|
||||||
make_option('--processes', dest='processes', default=4,
|
)
|
||||||
help='uWSGI number of processes.'),
|
parser.add_argument(
|
||||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
'--cert-country',
|
||||||
help='Tells Django to NOT prompt the user for input of any kind. '
|
dest='cert_country',
|
||||||
'You must use --username with --noinput, and must contain the '
|
default='ES',
|
||||||
'cleeryd process owner, which is the user how will perform tincd updates'),
|
help='Certificate Distinguished Name Country.'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
option_list = BaseCommand.option_list
|
'--cert-state',
|
||||||
help = 'Configures nginx + uwsgi to run with your Orchestra instance.'
|
dest='cert_state',
|
||||||
|
default='Spain',
|
||||||
|
help='Certificate Distinguished Name STATE.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cert-locality',
|
||||||
|
dest='cert_locality',
|
||||||
|
default='Barcelona',
|
||||||
|
help='Certificate Distinguished Name Country.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cert-org_name',
|
||||||
|
dest='cert_org_name',
|
||||||
|
default='Orchestra',
|
||||||
|
help='Certificate Distinguished Name Organization Name.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cert-org_unit',
|
||||||
|
dest='cert_org_unit',
|
||||||
|
default='DevOps',
|
||||||
|
help='Certificate Distinguished Name Organization Unity.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cert-email',
|
||||||
|
dest='cert_email',
|
||||||
|
default='orchestra@orchestra.lan',
|
||||||
|
help='Certificate Distinguished Name Email Address.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--cert-common_name',
|
||||||
|
dest='cert_common_name',
|
||||||
|
default=None,
|
||||||
|
help='Certificate Distinguished Name Common Name.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--server-name',
|
||||||
|
dest='server_name',
|
||||||
|
default='',
|
||||||
|
help='Nginx SSL certificate key.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--user',
|
||||||
|
dest='user',
|
||||||
|
default='',
|
||||||
|
help='uWSGI daemon user.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--group',
|
||||||
|
dest='group',
|
||||||
|
default='',
|
||||||
|
help='uWSGI daemon group.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--processes',
|
||||||
|
dest='processes',
|
||||||
|
default=4,
|
||||||
|
help='uWSGI number of processes.'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--noinput',
|
||||||
|
action='store_false',
|
||||||
|
dest='interactive',
|
||||||
|
default=True,
|
||||||
|
help='''Tells Django to NOT prompt the user for input of any kind.
|
||||||
|
You must use --username with --noinput, and must contain the
|
||||||
|
cleeryd process owner, which is the user how will perform tincd updates'''
|
||||||
|
)
|
||||||
|
|
||||||
def generate_certificate(self, **options):
|
def generate_certificate(self, **options):
|
||||||
override = options.get('cert_override')
|
override = options.get('cert_override')
|
||||||
|
|
|
@ -1,52 +1,97 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from orchestra.utils.sys import run, check_root
|
from orchestra.utils.sys import run, check_root
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
help = 'Setup Postfix.'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
self.option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--db_name', dest='db_name', default='orchestra',
|
|
||||||
help='Specifies the database to create.'),
|
|
||||||
make_option('--db_user', dest='db_user', default='orchestra',
|
|
||||||
help='Specifies the database to create.'),
|
|
||||||
make_option('--db_password', dest='db_password', default='orchestra',
|
|
||||||
help='Specifies the database to create.'),
|
|
||||||
make_option('--db_host', dest='db_host', default='localhost',
|
|
||||||
help='Specifies the database to create.'),
|
|
||||||
|
|
||||||
make_option('--vmail_username', dest='vmail_username', default='vmail',
|
def add_arguments(self, parser):
|
||||||
help='Specifies username in the operating system (default=vmail).'),
|
parser.add_argument(
|
||||||
make_option('--vmail_uid', dest='vmail_uid', default='5000',
|
'--db_name',
|
||||||
help='UID of user <vmail_username> (default=5000).'),
|
dest='db_name',
|
||||||
make_option('--vmail_groupname', dest='vmail_groupname', default='vmail',
|
default='orchestra',
|
||||||
help='Specifies the groupname in the operating system (default=vmail).'),
|
help='Specifies the database to create.'
|
||||||
make_option('--vmail_gid', dest='vmail_gid', default='5000',
|
)
|
||||||
help='GID of user <vmail_username> (default=5000).'),
|
parser.add_argument(
|
||||||
make_option('--vmail_home', dest='vmail_home', default='/var/vmail',
|
'--db_user',
|
||||||
help='$HOME of user <vmail_username> (default=/var/vmail).'),
|
dest='db_user',
|
||||||
|
default='orchestra',
|
||||||
make_option('--dovecot_dir', dest='dovecot_dir', default='/etc/dovecot',
|
help='Specifies the database to create.'
|
||||||
help='Dovecot root directory (default=/etc/dovecot).'),
|
)
|
||||||
|
parser.add_argument(
|
||||||
make_option('--postfix_dir', dest='postfix_dir', default='/etc/postfix',
|
'--db_password',
|
||||||
help='Postfix root directory (default=/etc/postfix).'),
|
dest='db_password',
|
||||||
|
default='orchestra',
|
||||||
make_option('--amavis_dir', dest='amavis_dir', default='/etc/amavis',
|
help='Specifies the database to create.'
|
||||||
help='Amavis root directory (default=/etc/amavis).'),
|
)
|
||||||
|
parser.add_argument(
|
||||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
'--db_host',
|
||||||
help='Tells Django to NOT prompt the user for input of any kind. '
|
dest='db_host',
|
||||||
'You must use --username with --noinput, and must contain the '
|
default='localhost',
|
||||||
'cleeryd process owner, which is the user how will perform tincd updates'),
|
help='Specifies the database to create.'
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
option_list = BaseCommand.option_list
|
'--vmail_username',
|
||||||
help = 'Setup Postfix.'
|
dest='vmail_username',
|
||||||
|
default='vmail',
|
||||||
|
help='Specifies username in the operating system (default=vmail).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--vmail_uid',
|
||||||
|
dest='vmail_uid',
|
||||||
|
default='5000',
|
||||||
|
help='UID of user <vmail_username> (default=5000).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--vmail_groupname',
|
||||||
|
dest='vmail_groupname',
|
||||||
|
default='vmail',
|
||||||
|
help='Specifies the groupname in the operating system (default=vmail).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--vmail_gid',
|
||||||
|
dest='vmail_gid',
|
||||||
|
default='5000',
|
||||||
|
help='GID of user <vmail_username> (default=5000).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--vmail_home',
|
||||||
|
dest='vmail_home',
|
||||||
|
default='/var/vmail',
|
||||||
|
help='$HOME of user <vmail_username> (default=/var/vmail).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dovecot_dir',
|
||||||
|
dest='dovecot_dir',
|
||||||
|
default='/etc/dovecot',
|
||||||
|
help='Dovecot root directory (default=/etc/dovecot).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--postfix_dir',
|
||||||
|
dest='postfix_dir',
|
||||||
|
default='/etc/postfix',
|
||||||
|
help='Postfix root directory (default=/etc/postfix).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--amavis_dir',
|
||||||
|
dest='amavis_dir',
|
||||||
|
default='/etc/amavis',
|
||||||
|
help='Amavis root directory (default=/etc/amavis).'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--noinput',
|
||||||
|
action='store_false',
|
||||||
|
dest='interactive',
|
||||||
|
default=True,
|
||||||
|
help='''Tells Django to NOT prompt the user for input of any kind.
|
||||||
|
You must use --username with --noinput, and must contain the
|
||||||
|
cleeryd process owner, which is the user how will perform tincd updates'''
|
||||||
|
)
|
||||||
|
|
||||||
@check_root
|
@check_root
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
@ -11,41 +10,64 @@ from orchestra.utils.sys import run, check_root
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
help = 'Setup PostgreSQL database.'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
# Get defaults from settings, if exists
|
# Get defaults from settings, if exists
|
||||||
try:
|
try:
|
||||||
defaults = settings.DATABASES['default']
|
self.defaults = settings.DATABASES['default']
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
defaults = {}
|
defaults = {}
|
||||||
else:
|
else:
|
||||||
if defaults['ENGINE'] != 'django.db.backends.postgresql_psycopg2':
|
if self.defaults['ENGINE'] != 'django.db.backends.postgresql_psycopg2':
|
||||||
defaults = {}
|
self.defaults = {}
|
||||||
|
|
||||||
self.option_list = BaseCommand.option_list + (
|
def add_arguments(self, parser):
|
||||||
make_option('--db_name', dest='db_name',
|
parser.add_argument(
|
||||||
default=defaults.get('DB_NAME', 'orchestra'),
|
'--db_name',
|
||||||
help='Specifies the database to create.'),
|
dest='db_name',
|
||||||
make_option('--db_user', dest='db_user',
|
default=self.defaults.get('DB_NAME', 'orchestra'),
|
||||||
default=defaults.get('DB_USER', 'orchestra'),
|
help='Specifies the database to create.',
|
||||||
help='Specifies the database to create.'),
|
type=str
|
||||||
make_option('--db_password', dest='db_password',
|
)
|
||||||
default=defaults.get('PASSWORD', ''),
|
parser.add_argument(
|
||||||
help='Specifies the database password, random if not specified.'),
|
'--db_user',
|
||||||
make_option('--db_host', dest='db_host',
|
dest='db_user',
|
||||||
default=defaults.get('HOST', 'localhost'),
|
default=self.defaults.get('DB_USER', 'orchestra'),
|
||||||
help='Specifies the database to create.'),
|
help='Specifies the database to create.',
|
||||||
make_option('--db_port', dest='db_port',
|
type=str
|
||||||
default=defaults.get('PORT', '5432'),
|
)
|
||||||
help='Specifies the database to create.'),
|
parser.add_argument(
|
||||||
make_option('--noinput', action='store_false', dest='interactive', default=True,
|
'--db_password',
|
||||||
help='Tells Django to NOT prompt the user for input of any kind. '
|
dest='db_password',
|
||||||
'You must use --username with --noinput, and must contain the '
|
default=self.defaults.get('PASSWORD', ''),
|
||||||
'cleeryd process owner, which is the user how will perform tincd updates'),
|
help='Specifies the database password, random if not specified.',
|
||||||
)
|
type=str
|
||||||
|
)
|
||||||
option_list = BaseCommand.option_list
|
parser.add_argument(
|
||||||
help = 'Setup PostgreSQL database.'
|
'--db_host',
|
||||||
|
dest='db_host',
|
||||||
|
default=self.defaults.get('HOST', 'localhost'),
|
||||||
|
help='Specifies the database to create.',
|
||||||
|
type=str
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--db_port',
|
||||||
|
dest='db_port',
|
||||||
|
default=self.defaults.get('PORT', '5432'),
|
||||||
|
help='Specifies the database to create.',
|
||||||
|
type=str
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--noinput',
|
||||||
|
action='store_false',
|
||||||
|
dest='interactive',
|
||||||
|
default=True,
|
||||||
|
help='''Tells Django to NOT prompt the user for input of any kind.
|
||||||
|
You must use --username with --noinput, and must contain the
|
||||||
|
cleeryd process owner, which is the user how will perform tincd updates'''
|
||||||
|
)
|
||||||
|
|
||||||
def run_postgres(self, cmd, *args, **kwargs):
|
def run_postgres(self, cmd, *args, **kwargs):
|
||||||
return run('su postgres -c "psql -c \\"%s\\""' % cmd, *args, **kwargs)
|
return run('su postgres -c "psql -c \\"%s\\""' % cmd, *args, **kwargs)
|
||||||
|
@ -82,7 +104,7 @@ class Command(BaseCommand):
|
||||||
self.stdout.write(msg % context)
|
self.stdout.write(msg % context)
|
||||||
else:
|
else:
|
||||||
raise CommandError("Postgres user '%(db_user)s' already exists and "
|
raise CommandError("Postgres user '%(db_user)s' already exists and "
|
||||||
"--db_pass has not been provided." % context)
|
"--db_password has not been provided." % context)
|
||||||
else:
|
else:
|
||||||
context['db_password'] = context['default_db_password']
|
context['db_password'] = context['default_db_password']
|
||||||
msg = "Created new Postgres user '%(db_user)s' with password '%(db_password)s'"
|
msg = "Created new Postgres user '%(db_user)s' with password '%(db_password)s'"
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from orchestra import settings
|
from orchestra import settings
|
||||||
|
@ -30,9 +28,16 @@ def flatten(nested, depth=0):
|
||||||
class ManageServiceCommand(BaseCommand):
|
class ManageServiceCommand(BaseCommand):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ManageServiceCommand, self).__init__(*args, **kwargs)
|
super(ManageServiceCommand, self).__init__(*args, **kwargs)
|
||||||
self.option_list = BaseCommand.option_list + tuple(
|
|
||||||
make_option('--no-%s' % service, action='store_false', dest=service, default=True,
|
|
||||||
help='Do not %s %s' % (self.action, service)) for service in flatten(self.services)
|
def add_arguments(self, parser):
|
||||||
|
for service in flatten(self.services):
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-%s' % service,
|
||||||
|
action='store_false',
|
||||||
|
dest=service,
|
||||||
|
default=True,
|
||||||
|
help='Do not %s %s' % (self.action, service)
|
||||||
)
|
)
|
||||||
|
|
||||||
@check_root
|
@check_root
|
||||||
|
@ -54,5 +59,4 @@ class ManageServiceCommand(BaseCommand):
|
||||||
class Command(ManageServiceCommand):
|
class Command(ManageServiceCommand):
|
||||||
services = settings.ORCHESTRA_START_SERVICES
|
services = settings.ORCHESTRA_START_SERVICES
|
||||||
action = 'start'
|
action = 'start'
|
||||||
option_list = BaseCommand.option_list
|
|
||||||
help = 'Start all related services. Usefull for reload configuration and files.'
|
help = 'Start all related services. Usefull for reload configuration and files.'
|
||||||
|
|
|
@ -7,5 +7,4 @@ from orchestra.management.commands.startservices import ManageServiceCommand
|
||||||
class Command(ManageServiceCommand):
|
class Command(ManageServiceCommand):
|
||||||
services = settings.ORCHESTRA_STOP_SERVICES
|
services = settings.ORCHESTRA_STOP_SERVICES
|
||||||
action = 'stop'
|
action = 'stop'
|
||||||
option_list = BaseCommand.option_list
|
|
||||||
help = 'Stop all related services. Usefull for reload configuration and files.'
|
help = 'Stop all related services. Usefull for reload configuration and files.'
|
||||||
|
|
|
@ -3,7 +3,6 @@ import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from distutils.sysconfig import get_python_lib
|
from distutils.sysconfig import get_python_lib
|
||||||
from optparse import make_option
|
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
@ -26,20 +25,28 @@ def get_existing_pip_installation():
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(Command, self).__init__(*args, **kwargs)
|
|
||||||
self.option_list = BaseCommand.option_list + (
|
|
||||||
make_option('--pip_only', action='store_true', dest='pip_only', default=False,
|
|
||||||
help='Only run "pip install django-orchestra --upgrade"'),
|
|
||||||
make_option('--orchestra_version', dest='version', default=False,
|
|
||||||
help='Specifies what version of the Orchestra you want to install'),
|
|
||||||
)
|
|
||||||
|
|
||||||
option_list = BaseCommand.option_list
|
|
||||||
help = "Upgrading Orchestra's installation. Desired version is accepted as argument"
|
help = "Upgrading Orchestra's installation. Desired version is accepted as argument"
|
||||||
can_import_settings = False
|
can_import_settings = False
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Command, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--pip_only',
|
||||||
|
action='store_true',
|
||||||
|
dest='pip_only',
|
||||||
|
default=False,
|
||||||
|
help='Only run "pip install django-orchestra --upgrade"'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--orchestra_version',
|
||||||
|
dest='version',
|
||||||
|
default=False,
|
||||||
|
help='Specifies what version of the Orchestra you want to install'
|
||||||
|
)
|
||||||
|
|
||||||
@check_root
|
@check_root
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
current_version = get_version()
|
current_version = get_version()
|
||||||
|
|
|
@ -1,8 +1,138 @@
|
||||||
FROM debian:latest
|
#
|
||||||
|
# NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"
|
||||||
|
#
|
||||||
|
# PLEASE DO NOT EDIT IT DIRECTLY.
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM buildpack-deps:buster
|
||||||
|
|
||||||
|
# ensure local python is preferred over distribution python
|
||||||
|
ENV PATH /usr/local/bin:$PATH
|
||||||
|
|
||||||
|
# http://bugs.python.org/issue19846
|
||||||
|
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
|
||||||
|
# extra dependencies (over what buildpack-deps already includes)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libbluetooth-dev \
|
||||||
|
tk-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV GPG_KEY 0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
|
||||||
|
ENV PYTHON_VERSION 3.6.12
|
||||||
|
|
||||||
|
RUN set -ex \
|
||||||
|
\
|
||||||
|
&& wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \
|
||||||
|
&& wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \
|
||||||
|
&& export GNUPGHOME="$(mktemp -d)" \
|
||||||
|
&& gpg --batch --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \
|
||||||
|
&& gpg --batch --verify python.tar.xz.asc python.tar.xz \
|
||||||
|
&& { command -v gpgconf > /dev/null && gpgconf --kill all || :; } \
|
||||||
|
&& rm -rf "$GNUPGHOME" python.tar.xz.asc \
|
||||||
|
&& mkdir -p /usr/src/python \
|
||||||
|
&& tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
|
||||||
|
&& rm python.tar.xz \
|
||||||
|
\
|
||||||
|
&& cd /usr/src/python \
|
||||||
|
&& gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
|
||||||
|
&& ./configure \
|
||||||
|
--build="$gnuArch" \
|
||||||
|
--enable-loadable-sqlite-extensions \
|
||||||
|
--enable-optimizations \
|
||||||
|
--enable-option-checking=fatal \
|
||||||
|
--enable-shared \
|
||||||
|
--with-system-expat \
|
||||||
|
--with-system-ffi \
|
||||||
|
--without-ensurepip \
|
||||||
|
&& make -j "$(nproc)" \
|
||||||
|
# setting PROFILE_TASK makes "--enable-optimizations" reasonable: https://bugs.python.org/issue36044 / https://github.com/docker-library/python/issues/160#issuecomment-509426916
|
||||||
|
PROFILE_TASK='-m test.regrtest --pgo \
|
||||||
|
test_array \
|
||||||
|
test_base64 \
|
||||||
|
test_binascii \
|
||||||
|
test_binhex \
|
||||||
|
test_binop \
|
||||||
|
test_bytes \
|
||||||
|
test_c_locale_coercion \
|
||||||
|
test_class \
|
||||||
|
test_cmath \
|
||||||
|
test_codecs \
|
||||||
|
test_compile \
|
||||||
|
test_complex \
|
||||||
|
test_csv \
|
||||||
|
test_decimal \
|
||||||
|
test_dict \
|
||||||
|
test_float \
|
||||||
|
test_fstring \
|
||||||
|
test_hashlib \
|
||||||
|
test_io \
|
||||||
|
test_iter \
|
||||||
|
test_json \
|
||||||
|
test_long \
|
||||||
|
test_math \
|
||||||
|
test_memoryview \
|
||||||
|
test_pickle \
|
||||||
|
test_re \
|
||||||
|
test_set \
|
||||||
|
test_slice \
|
||||||
|
test_struct \
|
||||||
|
test_threading \
|
||||||
|
test_time \
|
||||||
|
test_traceback \
|
||||||
|
test_unicode \
|
||||||
|
' \
|
||||||
|
&& make install \
|
||||||
|
&& rm -rf /usr/src/python \
|
||||||
|
\
|
||||||
|
&& find /usr/local -depth \
|
||||||
|
\( \
|
||||||
|
\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
|
||||||
|
-o \( -type f -a \( -name '*.pyc' -o -name '*.pyo' -o -name '*.a' \) \) \
|
||||||
|
-o \( -type f -a -name 'wininst-*.exe' \) \
|
||||||
|
\) -exec rm -rf '{}' + \
|
||||||
|
\
|
||||||
|
&& ldconfig \
|
||||||
|
\
|
||||||
|
&& python3 --version
|
||||||
|
|
||||||
|
# make some useful symlinks that are expected to exist
|
||||||
|
RUN cd /usr/local/bin \
|
||||||
|
&& ln -s idle3 idle \
|
||||||
|
&& ln -s pydoc3 pydoc \
|
||||||
|
&& ln -s python3 python \
|
||||||
|
&& ln -s python3-config python-config
|
||||||
|
|
||||||
|
# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
|
||||||
|
ENV PYTHON_PIP_VERSION 21.0
|
||||||
|
# https://github.com/pypa/get-pip
|
||||||
|
ENV PYTHON_GET_PIP_URL https://github.com/pypa/get-pip/raw/8cc88aca7d9775fce279e8b84ef163cf1d3e8a2e/get-pip.py
|
||||||
|
ENV PYTHON_GET_PIP_SHA256 ffb67da2e976f48dd29714fc64812d1ac419eb7d48079737166dd95640d1debd
|
||||||
|
|
||||||
|
RUN set -ex; \
|
||||||
|
\
|
||||||
|
wget -O get-pip.py "$PYTHON_GET_PIP_URL"; \
|
||||||
|
echo "$PYTHON_GET_PIP_SHA256 *get-pip.py" | sha256sum --check --strict -; \
|
||||||
|
\
|
||||||
|
python get-pip.py \
|
||||||
|
--disable-pip-version-check \
|
||||||
|
--no-cache-dir \
|
||||||
|
"pip==$PYTHON_PIP_VERSION" \
|
||||||
|
; \
|
||||||
|
pip --version; \
|
||||||
|
\
|
||||||
|
find /usr/local -depth \
|
||||||
|
\( \
|
||||||
|
\( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \
|
||||||
|
-o \
|
||||||
|
\( -type f -a \( -name '*.pyc' -o -name '*.pyo' \) \) \
|
||||||
|
\) -exec rm -rf '{}' +; \
|
||||||
|
rm -f get-pip.py
|
||||||
|
|
||||||
RUN apt-get -y update && apt-get install -y curl sudo
|
RUN apt-get -y update && apt-get install -y curl sudo
|
||||||
|
|
||||||
RUN export TERM=xterm; curl -L http://git.io/orchestra-admin | bash -s install_requirements
|
RUN export TERM=xterm; curl -L https://raw.githubusercontent.com/ribaguifi/django-orchestra/master/orchestra/bin/orchestra-admin | bash -s install_requirements
|
||||||
|
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
|
|
||||||
|
@ -10,3 +140,5 @@ RUN useradd orchestra --shell /bin/bash && \
|
||||||
{ echo "orchestra:orchestra" | chpasswd; } && \
|
{ echo "orchestra:orchestra" | chpasswd; } && \
|
||||||
mkhomedir_helper orchestra && \
|
mkhomedir_helper orchestra && \
|
||||||
adduser orchestra sudo
|
adduser orchestra sudo
|
||||||
|
|
||||||
|
CMD ["python3"]
|
||||||
|
|
|
@ -46,13 +46,16 @@ function install_orchestra () {
|
||||||
|
|
||||||
if [[ $dev ]]; then
|
if [[ $dev ]]; then
|
||||||
# Install from source
|
# Install from source
|
||||||
python_path=$(python3 -c "import sys; print([path for path in sys.path if path.startswith('/usr/local/lib/python')][0]);")
|
python_path=$(python3 -c "import sys; print([path for path in sys.path if path.startswith('/usr/local/lib/python')][1]);")
|
||||||
if [[ -d $python_path/orchestra ]]; then
|
if [[ -d $python_path/orchestra ]]; then
|
||||||
run sudo rm -fr $python_path/orchestra
|
run sudo rm -fr $python_path/orchestra
|
||||||
fi
|
fi
|
||||||
orch_version=$(python3 -c "from orchestra import get_version; print(get_version());" 2> /dev/null)
|
orch_version=$(python3 -c "from orchestra import get_version; print(get_version());" 2> /dev/null || echo '')
|
||||||
if [[ ! $orch_version ]]; then
|
if [[ ! $orch_version ]]; then
|
||||||
# First Orchestra installation
|
# First Orchestra installation
|
||||||
|
run sudo mkdir -p /usr/share/man/man1
|
||||||
|
run sudo mkdir -p /usr/share/man/man7
|
||||||
|
run sudo apt-get update
|
||||||
run sudo apt-get -y install git python3-pip
|
run sudo apt-get -y install git python3-pip
|
||||||
surun "git clone $repo $home/django-orchestra" || {
|
surun "git clone $repo $home/django-orchestra" || {
|
||||||
# Finishing partial installation
|
# Finishing partial installation
|
||||||
|
@ -65,13 +68,12 @@ function install_orchestra () {
|
||||||
if [[ -L /usr/local/bin/orchestra-admin || -f /usr/local/bin/orchestra-admin ]]; then
|
if [[ -L /usr/local/bin/orchestra-admin || -f /usr/local/bin/orchestra-admin ]]; then
|
||||||
run sudo rm -f /usr/local/bin/{orchestra-admin,orchestra-beat}
|
run sudo rm -f /usr/local/bin/{orchestra-admin,orchestra-beat}
|
||||||
fi
|
fi
|
||||||
run sudo ln -s $home/django-orchestra/orchestra/bin/orchestra-admin /usr/local/bin/
|
run sudo pip3 install -e $home/django-orchestra
|
||||||
run sudo ln -s $home/django-orchestra/orchestra/bin/orchestra-beat /usr/local/bin/
|
|
||||||
run sudo orchestra-admin install_requirements --testing
|
run sudo orchestra-admin install_requirements --testing
|
||||||
else
|
else
|
||||||
# Install from pip
|
# Install from pip
|
||||||
run sudo pip3 install http://git.io/django-orchestra-dev
|
|
||||||
run sudo orchestra-admin install_requirements
|
run sudo orchestra-admin install_requirements
|
||||||
|
run sudo pip3 install -e git+https://github.com/ribaguifi/django-orchestra.git#egg=django-orchestra
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,12 +81,13 @@ function install_orchestra () {
|
||||||
function setup_database () {
|
function setup_database () {
|
||||||
dev=$1
|
dev=$1
|
||||||
noinput=$2
|
noinput=$2
|
||||||
run sudo apt-get install -y postgresql python3-psycopg2
|
run sudo apt-get install -y postgresql
|
||||||
|
run sudo pip install psycopg2
|
||||||
# Setup Database
|
# Setup Database
|
||||||
if [[ $dev ]]; then
|
if [[ $dev ]]; then
|
||||||
# Speeding up tests, don't do this in production!
|
# Speeding up tests, don't do this in production!
|
||||||
. /usr/share/postgresql-common/init.d-functions
|
. /usr/share/postgresql-common/init.d-functions
|
||||||
pg_version=$(psql --version | head -n1 | sed -r "s/^.*\s([0-9]+\.[0-9]+).*/\1/")
|
pg_version=$(psql --version | head -n1 | awk '{print $3}' | sed -e "s,\..*,,")
|
||||||
sudo sed -i \
|
sudo sed -i \
|
||||||
-e "s/^#fsync =\s*.*/fsync = off/" \
|
-e "s/^#fsync =\s*.*/fsync = off/" \
|
||||||
-e "s/^#full_page_writes =\s*.*/full_page_writes = off/" \
|
-e "s/^#full_page_writes =\s*.*/full_page_writes = off/" \
|
||||||
|
@ -170,7 +173,7 @@ function main () {
|
||||||
dev=
|
dev=
|
||||||
noinput=
|
noinput=
|
||||||
user=$(whoami)
|
user=$(whoami)
|
||||||
repo='https://github.com/glic3rinu/django-orchestra.git'
|
repo='https://github.com/ribaguifi/django-orchestra.git'
|
||||||
brepo=
|
brepo=
|
||||||
project_name="panel"
|
project_name="panel"
|
||||||
bproject_name=
|
bproject_name=
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
Django==1.10.5
|
||||||
|
django-fluent-dashboard==0.6.1
|
||||||
|
django-admin-tools==0.8.0
|
||||||
|
django-extensions==1.7.4
|
||||||
|
django-celery==3.1.17
|
||||||
|
celery==3.1.23
|
||||||
|
kombu==3.0.35
|
||||||
|
billiard==3.3.0.23
|
||||||
|
Markdown==2.4
|
||||||
|
djangorestframework==3.4.7
|
||||||
|
ecdsa==0.11
|
||||||
|
Pygments==1.6
|
||||||
|
django-filter==0.15.2
|
||||||
|
jsonfield==0.9.22
|
||||||
|
python-dateutil==2.2
|
||||||
|
django-iban==0.3.0
|
||||||
|
requests
|
||||||
|
phonenumbers
|
||||||
|
django-countries
|
||||||
|
django-localflavor
|
||||||
|
amqp
|
||||||
|
anyjson
|
||||||
|
pytz
|
||||||
|
cracklib
|
||||||
|
lxml==3.3.5
|
||||||
|
selenium
|
||||||
|
xvfbwrapper
|
||||||
|
freezegun
|
||||||
|
coverage
|
||||||
|
flake8
|
||||||
|
django-debug-toolbar==1.3.0
|
||||||
|
django-nose==1.4.4
|
||||||
|
sqlparse
|
||||||
|
pyinotify
|
||||||
|
PyMySQL
|
||||||
|
dj_database_url==0.5.0
|
||||||
|
psycopg2-binary
|
||||||
|
python-decouple
|
||||||
|
https://github.com/glic3rinu/passlib/archive/master.zip
|
Loading…
Reference in New Issue