diff --git a/.env.local b/.env.local deleted file mode 100644 index dc9aadb..0000000 --- a/.env.local +++ /dev/null @@ -1,13 +0,0 @@ -SECRET_KEY='django-secret' - -DJANGO_SUPERUSER_USERNAME='live_admin' -DJANGO_SUPERUSER_PASSWORD='admin-secret' -DJANGO_SUPERUSER_EMAIL='sumanth@hacktj.org' - -SENDGRID_API_KEY='SG.KEY' - -POSTGRES_PASSWORD='817m5da7fyleau^108yko2ib!&+*!0ba38gh%g8ps()56)=gsv' -POSTGRES_USER='live_postgres' -POSTGRES_DB='hacktj_live' -POSTGRES_INITDB_ARGS='--auth-host=scram-sha-256 --auth-local=scram-sha-256 --data-checksums' -POSTGRES_HOST_AUTH_METHOD='scram-sha-256' diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 8c214fb..326d2e6 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, 3.10] + python-version: ['3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 @@ -43,8 +43,17 @@ jobs: - name: Run Tests run: | - SECRET_KEY=9 poetry run python manage.py createsecrets --exclude superuser --exclude sendgrid --exclude postgres --output ./.env - export $(grep -v '^#' .env | xargs) + poetry run python manage.py createsecrets + + export SECRET_KEY="$(cat ./compose/secrets/development/secret_key.txt)" + export DJANGO_SUPERUSER_USERNAME="$(cat ./compose/secrets/development/django_superuser_username.txt)" + export DJANGO_SUPERUSER_PASSWORD="$(cat ./compose/secrets/development/django_superuser_password.txt)" + export DJANGO_SUPERUSER_EMAIL="$(cat ./compose/secrets/development/django_superuser_email.txt)" + export SENDGRID_API_KEY="$(cat ./compose/secrets/development/sendgrid_api_key.txt)" + export POSTGRES_PASSWORD="$(cat ./compose/secrets/development/postgres_password.txt)" + export POSTGRES_USER="$(cat ./compose/secrets/development/postgres_user.txt)" + export POSTGRES_DB="$(cat ./compose/secrets/development/postgres_db.txt)" + poetry run python manage.py migrate poetry run python -Wa manage.py check --deploy poetry run python -Wa manage.py test diff --git a/.gitignore b/.gitignore index b61f997..bf88065 100644 --- a/.gitignore +++ b/.gitignore @@ -30,7 +30,6 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -60,6 +59,7 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -84,6 +84,7 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook @@ -94,7 +95,9 @@ profile_default/ ipython_config.py # pyenv -.python-version +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -140,6 +143,11 @@ dmypy.json # Pyre type checker .pyre/ -# Ignore redis binary dump (dump.rdb) files +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ +# Ignore redis binary dump (dump.rdb) files *.rdb diff --git a/compose/django/entrypoint.sh b/compose/django/entrypoint.sh index 49933e6..a6a9037 100755 --- a/compose/django/entrypoint.sh +++ b/compose/django/entrypoint.sh @@ -1,6 +1,17 @@ #!/bin/sh set -e +# these are all paths to files under /run/secrets +export \ + SECRET_KEY="$(cat $SECRET_KEY)" \ + DJANGO_SUPERUSER_USERNAME="$(cat $DJANGO_SUPERUSER_USERNAME)" \ + DJANGO_SUPERUSER_PASSWORD="$(cat $DJANGO_SUPERUSER_PASSWORD)" \ + DJANGO_SUPERUSER_EMAIL="$(cat $DJANGO_SUPERUSER_EMAIL)" \ + POSTGRES_USER="$(cat $POSTGRES_USER)" \ + POSTGRES_PASSWORD="$(cat $POSTGRES_PASSWORD)" \ + POSTGRES_DB="$(cat $POSTGRES_DB)" \ + SENDGRID_API_KEY="$(cat $SENDGRID_API_KEY)" + urlencode () { python3 - "$@" << END from urllib.parse import quote_plus @@ -12,6 +23,7 @@ print(quote_plus(" ".join(argv[1:]))) END } +DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:${POSTGRES_PORT:-5432}/${POSTGRES_DB}" ENCODED_DATABASE_URL=$(urlencode "$DATABASE_URL") until pg_isready --dbname="$ENCODED_DATABASE_URL" --host="postgres" --username="$POSTGRES_USER"; do diff --git a/compose/secrets/development/django_superuser_email.txt b/compose/secrets/development/django_superuser_email.txt new file mode 100644 index 0000000..b27765a --- /dev/null +++ b/compose/secrets/development/django_superuser_email.txt @@ -0,0 +1 @@ +sumanth@hacktj.org diff --git a/compose/secrets/development/django_superuser_password.txt b/compose/secrets/development/django_superuser_password.txt new file mode 100644 index 0000000..829b02e --- /dev/null +++ b/compose/secrets/development/django_superuser_password.txt @@ -0,0 +1 @@ +admin-secret diff --git a/compose/secrets/development/django_superuser_username.txt b/compose/secrets/development/django_superuser_username.txt new file mode 100644 index 0000000..433f639 --- /dev/null +++ b/compose/secrets/development/django_superuser_username.txt @@ -0,0 +1 @@ +live_admin diff --git a/compose/secrets/development/postgres_db.txt b/compose/secrets/development/postgres_db.txt new file mode 100644 index 0000000..83f3a6b --- /dev/null +++ b/compose/secrets/development/postgres_db.txt @@ -0,0 +1 @@ +hacktj_live diff --git a/compose/secrets/development/postgres_password.txt b/compose/secrets/development/postgres_password.txt new file mode 100644 index 0000000..0a83a6a --- /dev/null +++ b/compose/secrets/development/postgres_password.txt @@ -0,0 +1 @@ +817m5da7fyleau^108yko2ib!&+*!0ba38gh%g8ps()56)=gsv diff --git a/compose/secrets/development/postgres_user.txt b/compose/secrets/development/postgres_user.txt new file mode 100644 index 0000000..f583f2e --- /dev/null +++ b/compose/secrets/development/postgres_user.txt @@ -0,0 +1 @@ +live_postgres diff --git a/compose/secrets/development/secret_key.txt b/compose/secrets/development/secret_key.txt new file mode 100644 index 0000000..9cb6afc --- /dev/null +++ b/compose/secrets/development/secret_key.txt @@ -0,0 +1 @@ +django-secret diff --git a/compose/secrets/development/sendgrid_api_key.txt b/compose/secrets/development/sendgrid_api_key.txt new file mode 100644 index 0000000..d8cf78b --- /dev/null +++ b/compose/secrets/development/sendgrid_api_key.txt @@ -0,0 +1 @@ +SG.KEY diff --git a/compose/secrets/production/.gitkeep b/compose/secrets/production/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index d628dac..534d4ba 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,4 +1,4 @@ -version: '3.6' +version: '3.9' services: django: @@ -9,18 +9,24 @@ services: - .:/app ports: - "8000:8000" - env_file: - - .env.local environment: - DEBUG=true - NODE_ENV=development - postgres: - env_file: - - .env.local - - redis: - build: ./compose/redis - restart: always - sysctls: - net.core.somaxconn: '511' +secrets: + secret_key: + file: ./compose/secrets/development/secret_key.txt + django_superuser_username: + file: ./compose/secrets/development/django_superuser_username.txt + django_superuser_password: + file: ./compose/secrets/development/django_superuser_password.txt + django_superuser_email: + file: ./compose/secrets/development/django_superuser_email.txt + postgres_user: + file: ./compose/secrets/development/postgres_user.txt + postgres_password: + file: ./compose/secrets/development/postgres_password.txt + postgres_db: + file: ./compose/secrets/development/postgres_db.txt + sendgrid_api_key: + file: ./compose/secrets/development/sendgrid_api_key.txt diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 720a6bf..de8108e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,34 +1,27 @@ -version: '3.6' +version: '3.9' services: django: build: args: - DEBUG=false - ports: - # - '80:8000' - - '8000:8000' - # expose: - # - 8000 volumes: - ./static:/app/static - ./backups:/app/backup - ./logs:/app/logs + ports: + - "8000:8000" deploy: resources: # for Gunicorn # https://github.com/benoitc/gunicorn/issues/1440#issuecomment-411785926 limits: cpus: "0.1" memory: "4G" - env_file: - - .env environment: - DEBUG=false - NODE_ENV=production postgres: - env_file: - - .env volumes: - ./data:/var/lib/postgresql/data @@ -45,3 +38,21 @@ services: # LISTEN_IPV6: 'true' # ERROR_LOG: stdout # ACCESS_LOG: stderr + +secrets: + secret_key: + file: ./compose/secrets/production/secret_key.txt + django_superuser_username: + file: ./compose/secrets/production/django_superuser_username.txt + django_superuser_password: + file: ./compose/secrets/production/django_superuser_password.txt + django_superuser_email: + file: ./compose/secrets/production/django_superuser_email.txt + postgres_user: + file: ./compose/secrets/production/postgres_user.txt + postgres_password: + file: ./compose/secrets/production/postgres_password.txt + postgres_db: + file: ./compose/secrets/production/postgres_db.txt + sendgrid_api_key: + file: ./compose/secrets/production/sendgrid_api_key.txt diff --git a/docker-compose.yml b/docker-compose.yml index 4219041..b8b2a82 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.6' +version: '3.9' services: django: @@ -13,15 +13,34 @@ services: net.core.somaxconn: 2048 command: ./compose/django/start.sh environment: - - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:${POSTGRES_PORT:-5432}/${POSTGRES_DB} + - SECRET_KEY=/run/secrets/secret_key + - DJANGO_SUPERUSER_USERNAME=/run/secrets/django_superuser_username + - DJANGO_SUPERUSER_PASSWORD=/run/secrets/django_superuser_password + - DJANGO_SUPERUSER_EMAIL=/run/secrets/django_superuser_email + - POSTGRES_USER=/run/secrets/postgres_user + - POSTGRES_PASSWORD=/run/secrets/postgres_password + - POSTGRES_PORT=5432 + - POSTGRES_DB=/run/secrets/postgres_db + - SENDGRID_API_KEY=/run/secrets/sendgrid_api_key - DOCKER=true + # - POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256 --auth-local=scram-sha-256 --data-checksums + # - POSTGRES_HOST_AUTH_METHOD=scram-sha-256 depends_on: - postgres - redis - memcached volumes: - - ./judge/fixtures:/app/judge/fixtures - ./utils/fixtures:/app/utils/fixtures + - ./judge/fixtures:/app/judge/fixtures + secrets: + - secret_key + - django_superuser_username + - django_superuser_password + - django_superuser_email + - postgres_user + - postgres_password + - postgres_db + - sendgrid_api_key postgres: image: postgres:14.2-alpine3.15 @@ -29,12 +48,22 @@ services: restart: always shm_size: 256MB environment: - # - PGHOST=postgres + - POSTGRES_USER_FILE=/run/secrets/postgres_user + - POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password + - POSTGRES_PORT=5432 + - POSTGRES_DB_FILE=/run/secrets/postgres_db - PGPORT=${POSTGRES_PORT:-5432} + # - PGHOST=postgres # - PGDATABASE=${POSTGRES_DB} # - PGUSER=${POSTGRES_USER} # - PGPASSWORD=${POSTGRES_PASSWORD} # - PGOPTIONS=${POSTGRES_INITDB_ARGS} + - POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256 --auth-local=scram-sha-256 --data-checksums + - POSTGRES_HOST_AUTH_METHOD=scram-sha-256 + secrets: + - postgres_user + - postgres_password + - postgres_db redis: build: ./compose/redis @@ -45,3 +74,23 @@ services: memcached: image: memcached:1.6.14-alpine3.15 restart: always + +secrets: + # just to keep `docker compose` from complaining about + # `Service "postgres" uses an undefined secret` + secret_key: + external: true + django_superuser_username: + external: true + django_superuser_password: + external: true + django_superuser_email: + external: true + postgres_user: + external: true + postgres_password: + external: true + postgres_db: + external: true + sendgrid_api_key: + external: true diff --git a/docs/deployment/self-hosted.md b/docs/deployment/self-hosted.md index b5494c9..5f10633 100644 --- a/docs/deployment/self-hosted.md +++ b/docs/deployment/self-hosted.md @@ -22,11 +22,11 @@ Thanks for using HackTJ Live for your event! Here's a quick guide to running Liv #### Setup 1. Clone the Live repository and enter the directory: `git clone https://github.com/HackTJ/live ~/live && cd ~/live` -2. Prepare the secrets file. (two options) +2. Prepare the secrets files. (two options) - `docker compose exec django poetry run python manage.py createsecrets` - - manually edit the template - 1. copy the template: `cp .env.local .env` - 2. update the information in `.env` (superuser information, SendGrid API key, Postgres password). You can generate passwords using something like `cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1` + - manually create the files + 1. copy the template: `cp ./compose/secrets/development/*.txt ./compose/secrets/production` + 2. update the secrets. All files in the [`./compose/secrets`](./compose/secrets) directory are configurable. You can generate secrets using something like `cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1` 3. Spin up the instance: `./start_live prod` and grab a cup of coffee or something as the system initializes. Once that's all started, the next step is to set up the webserver. ##### Nginx Setup diff --git a/docs/development.md b/docs/development.md index 3c08ec4..2dca97b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -34,15 +34,15 @@ poetry run pre-commit install #### Database Setup ```sh -export POSTGRES_USER=$(awk -F "\'" '/POSTGRES_USER/ { print $2; }' .env.local) +export POSTGRES_USER="$(cat ./compose/secrets/development/postgres_user.txt)" dropdb "$POSTGRES_USER" -export POSTGRES_PASSWORD=$(awk -F "\'" '/POSTGRES_PASSWORD/ { print $2; }' .env.local) +export POSTGRES_PASSWORD="$(cat ./compose/secrets/development/postgres_password.txt)" pg_ctl --pgdata=/usr/local/var/postgres initdb -U "$POSTGRES_USER" -P $POSTGRES_PASSWORD pg_ctl --pgdata=/usr/local/var/postgres start createdb "$POSTGRES_USER" psql "$POSTGRES_USER" --file=./docs/setup.sql -export SECRET_KEY=$(awk -F "\'" '/SECRET_KEY/ { print $2; }' .env.local) +export SECRET_KEY="$(cat ./compose/secrets/development/secret_key.txt)" poetry run python manage.py migrate unset POSTGRES_USER @@ -52,7 +52,6 @@ unset SECRET_KEY ### Making Changes -- If you update the [.env.local](./.env.local) file, make sure you surround all values with single quotes. If you rename the file, make sure to update this document with the correct name. - If you have direct commit/push access, be careful. Create PRs for big changes or any changes that might require testing, e.g., CI configuration updates. - ["Squash and merge"](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-request-merges#squash-and-merge-your-pull-request-commits) all PRs. -- If you make any changes to the front-end/templates, make sure to run `pushd tailwind/ && NODE_ENV=production yarn build && popd`. +- If you make any changes to the front-end (the Django templates), make sure to run `pushd tailwind/ && NODE_ENV=production yarn run build && popd`. diff --git a/hacktj_live/settings.py b/hacktj_live/settings.py index 1c23973..996b62b 100644 --- a/hacktj_live/settings.py +++ b/hacktj_live/settings.py @@ -14,6 +14,7 @@ from collections import OrderedDict from datetime import datetime from subprocess import run as run_cmd +from pathlib import Path import os from dj_database_url import parse as parse_db_url from utils.environment import ( @@ -21,15 +22,16 @@ is_netcat_available, get_current_ip, pg_isready, + load_secret, ) -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ -SECRET_KEY = os.getenv("SECRET_KEY", "") +SECRET_KEY = load_secret("SECRET_KEY", "") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.getenv("DEBUG", "false").upper() == "TRUE" @@ -133,7 +135,7 @@ ROOT_URLCONF = "hacktj_live.urls" -TEMPLATE_DIR = os.path.join(BASE_DIR, "templates") +TEMPLATE_DIR = BASE_DIR / "templates" TEMPLATES = [ { @@ -170,7 +172,13 @@ else: EMAIL_BACKEND = "sendgrid_backend.SendgridBackend" - SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY") + EMAIL_HOST = "smtp.sendgrid.net" + + EMAIL_HOST_PASSWORD = load_secret("SENDGRID_API_KEY") + + EMAIL_HOST_USER = "apikey" + + EMAIL_PORT = 587 EMAIL_SUBJECT_PREFIX = "[HackTJ Live] " @@ -233,10 +241,28 @@ # "OPTIONS": {"sslmode": "require"}, }, } +elif ( + "POSTGRES_USER" in os.environ + and "POSTGRES_PASSWORD" in os.environ + and "POSTGRES_PORT" in os.environ + and "POSTGRES_DB" in os.environ +): + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": load_secret("POSTGRES_DB"), + "USER": load_secret("POSTGRES_USER"), + "PASSWORD": load_secret("POSTGRES_PASSWORD"), + "HOST": "postgres", + "PORT": load_secret("POSTGRES_PORT"), + "CONN_MAX_AGE": 600, + # "OPTIONS": {"sslmode": "require"}, + }, + } elif "DATABASE_URL" in os.environ: DATABASES = { "default": parse_db_url( - os.environ["DATABASE_URL"], + load_secret("DATABASE_URL"), conn_max_age=600, # ssl_require=True, ), @@ -244,7 +270,7 @@ elif pg_isready( dbname="hacktj_live", host="127.0.0.1", port="5432", username="live_postgres" ): - # TODO: load these from vars, don't hard-code. see the .env.local file + # TODO: load these from vars, don't hard-code. see the `./compose/secrets/development/` directory DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", @@ -261,14 +287,14 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + "NAME": BASE_DIR / "db.sqlite3", } } DBBACKUP_STORAGE = "django.core.files.storage.FileSystemStorage" -DBBACKUP_STORAGE_OPTIONS = {"location": os.path.join(BASE_DIR, "backup")} +DBBACKUP_STORAGE_OPTIONS = {"location": BASE_DIR / "backup"} DBBACKUP_CONNECTOR_MAPPING = { "django.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpBinaryConnector", @@ -367,9 +393,10 @@ "file": { "level": "INFO", "class": "logging.FileHandler", - "filename": os.path.join( - BASE_DIR, "logs", "debug" if DEBUG else "production", "django.log" - ), + "filename": BASE_DIR + / "logs" + / ("debug" if DEBUG else "production") + / "django.log", }, }, "root": { @@ -381,7 +408,7 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_ROOT = os.path.join(BASE_DIR, "static") +STATIC_ROOT = BASE_DIR / "static" STATIC_URL = "/static/" diff --git a/poetry.lock b/poetry.lock index 0439923..4cbce03 100644 --- a/poetry.lock +++ b/poetry.lock @@ -437,7 +437,7 @@ django = "*" [[package]] name = "django-compressor" -version = "3.1" +version = "4.0" description = "Compresses linked and inline JavaScript or CSS into single cached files." category = "main" optional = false @@ -743,15 +743,15 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "2.5.1" +version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" [package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] [[package]] name = "pre-commit" @@ -1132,11 +1132,11 @@ twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tzdata" @@ -1234,7 +1234,7 @@ python-versions = "*" [[package]] name = "websockets" -version = "10.2" +version = "10.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false @@ -1270,7 +1270,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = ">=3.8,<4.0" -content-hash = "91f97ccd4a315d0288ebe884f1bf47d064746407de9898986512521cd47205eb" +content-hash = "fa4147571b26bec3dcb470d5b7fd5b45e90313984f7a8c8ade4cd2195b1f3558" [metadata.files] aioredis = [ @@ -1346,10 +1346,13 @@ automat = [ {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, ] bcrypt = [ + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"}, + {file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"}, {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, @@ -1396,6 +1399,9 @@ brotli = [ {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, @@ -1407,12 +1413,18 @@ brotli = [ {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, @@ -1420,6 +1432,9 @@ brotli = [ {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, @@ -1427,6 +1442,9 @@ brotli = [ {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, @@ -1572,8 +1590,8 @@ django-appconf = [ {file = "django_appconf-1.0.5-py3-none-any.whl", hash = "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d"}, ] django-compressor = [ - {file = "django_compressor-3.1-py2.py3-none-any.whl", hash = "sha256:89f7ba86777b30672c2f9c7557bf2aff87c5890903c73b1fa3ae38acd143e855"}, - {file = "django_compressor-3.1.tar.gz", hash = "sha256:c4a87bf65f9a534cfaf1c321a000a229c24e50c6d62ba6ab089482db42e819d9"}, + {file = "django_compressor-4.0-py2.py3-none-any.whl", hash = "sha256:b4fe15cc23bf39420b37cb0030572bd0971104ca1ec3764f502c0f179e576dff"}, + {file = "django_compressor-4.0.tar.gz", hash = "sha256:1db91b6d04293636a68bd1328dc7bb90d636b0295f67b1cc6d4fa102b9fd25f6"}, ] django-dbbackup = [] django-debug-toolbar = [ @@ -1875,8 +1893,8 @@ pillow = [ {file = "Pillow-9.1.0.tar.gz", hash = "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97"}, ] platformdirs = [ - {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, - {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, ] pre-commit = [ {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, @@ -2130,8 +2148,8 @@ txaio = [ {file = "txaio-22.2.1.tar.gz", hash = "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] tzdata = [ {file = "tzdata-2022.1-py2.py3-none-any.whl", hash = "sha256:238e70234214138ed7b4e8a0fab0e5e13872edab3be586ab8198c407620e2ab9"}, @@ -2176,54 +2194,54 @@ wcwidth = [ {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] websockets = [ - {file = "websockets-10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5396710f86a306cf52f87fd8ea594a0e894ba0cc5a36059eaca3a477dc332aa"}, - {file = "websockets-10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b22bdc795e62e71118b63e14a08bacfa4f262fd2877de7e5b950f5ac16b0348f"}, - {file = "websockets-10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b04270b5613f245ec84bb2c6a482a9d009aefad37c0575f6cda8499125d5d5c"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5c335dc0e7dc271ef36df3f439868b3c790775f345338c2f61a562f1074187b"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a009eb551c46fd79737791c0c833fc0e5b56bcd1c3057498b262d660b92e9cd"}, - {file = "websockets-10.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a10c0c1ee02164246f90053273a42d72a3b2452a7e7486fdae781138cf7fbe2d"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b38a5c9112e3dbbe45540f7b60c5204f49b3cb501b40950d6ab34cd202ab1d0"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2aa9b91347ecd0412683f28aabe27f6bad502d89bd363b76e0a3508b1596402e"}, - {file = "websockets-10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b7fe45ae43ac814beb8ca09d6995b56800676f2cfa8e23f42839dc69bba34a42"}, - {file = "websockets-10.2-cp310-cp310-win32.whl", hash = "sha256:cef40a1b183dcf39d23b392e9dd1d9b07ab9c46aadf294fff1350fb79146e72b"}, - {file = "websockets-10.2-cp310-cp310-win_amd64.whl", hash = "sha256:c21a67ab9a94bd53e10bba21912556027fea944648a09e6508415ad14e37c325"}, - {file = "websockets-10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb316b87cbe3c0791c2ad92a5a36bf6adc87c457654335810b25048c1daa6fd5"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f14bd10e170abc01682a9f8b28b16e6f20acf6175945ef38db6ffe31b0c72c3f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fa35c5d1830d0fb7b810324e9eeab9aa92e8f273f11fdbdc0741dcded6d72b9f"}, - {file = "websockets-10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:71a4491cfe7a9f18ee57d41163cb6a8a3fa591e0f0564ca8b0ed86b2a30cced4"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6193bbc1ee63aadeb9a4d81de0e19477401d150d506aee772d8380943f118186"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8beac786a388bb99a66c3be4ab0fb38273c0e3bc17f612a4e0a47c4fc8b9c045"}, - {file = "websockets-10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c67d9cacb3f6537ca21e9b224d4fd08481538e43bcac08b3d93181b0816def39"}, - {file = "websockets-10.2-cp37-cp37m-win32.whl", hash = "sha256:a03a25d95cc7400bd4d61a63460b5d85a7761c12075ee2f51de1ffe73aa593d3"}, - {file = "websockets-10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f8296b8408ec6853b26771599990721a26403e62b9de7e50ac0a056772ac0b5e"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7bb9d8a6beca478c7e9bdde0159bd810cc1006ad6a7cb460533bae39da692ca2"}, - {file = "websockets-10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:05f6e9757017270e7a92a2975e2ae88a9a582ffc4629086fd6039aa80e99cd86"}, - {file = "websockets-10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1c9031e90ebfc486e9cdad532b94004ade3aa39a31d3c46c105bb0b579cd2490"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82bc33db6d8309dc27a3bee11f7da2288ad925fcbabc2a4bb78f7e9c56249baf"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:24b879ba7db12bb525d4e58089fcbe6a3df3ce4666523183654170e86d372cbe"}, - {file = "websockets-10.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf931c33db9c87c53d009856045dd524e4a378445693382a920fa1e0eb77c36c"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:669e54228a4d9457abafed27cbf0e2b9f401445c4dfefc12bf8e4db9751703b8"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bffc65442dd35c473ca9790a3fa3ba06396102a950794f536783f4b8060af8dd"}, - {file = "websockets-10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4d110a84b63c5cfdd22485acc97b8b919aefeecd6300c0c9d551e055b9a88ea"}, - {file = "websockets-10.2-cp38-cp38-win32.whl", hash = "sha256:117383d0a17a0dda349f7a8790763dde75c1508ff8e4d6e8328b898b7df48397"}, - {file = "websockets-10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b66421f9f13d4df60cd48ab977ed2c2b6c9147ae1a33caf5a9f46294422fda1"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac081aa0307f263d63c5ff0727935c736c8dad51ddf2dc9f5d0c4759842aefaa"}, - {file = "websockets-10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4059e2ccbe6587b6dc9a01db5fc49ead9a884faa4076eea96c5ec62cb32f42a"}, - {file = "websockets-10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9ca2ca05a4c29179f06cf6727b45dba5d228da62623ec9df4184413d8aae6cb9"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97950c7c844ec6f8d292440953ae18b99e3a6a09885e09d20d5e7ecd9b914cf8"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:98f57b3120f8331cd7440dbe0e776474f5e3632fdaa474af1f6b754955a47d71"}, - {file = "websockets-10.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a72b92f96e5e540d5dda99ee3346e199ade8df63152fa3c737260da1730c411f"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:038afef2a05893578d10dadbdbb5f112bd115c46347e1efe99f6a356ff062138"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f09f46b1ff6d09b01c7816c50bd1903cf7d02ebbdb63726132717c2fcda835d5"}, - {file = "websockets-10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2349fa81b6b959484bb2bda556ccb9eb70ba68987646a0f8a537a1a18319fb03"}, - {file = "websockets-10.2-cp39-cp39-win32.whl", hash = "sha256:bef03a51f9657fb03d8da6ccd233fe96e04101a852f0ffd35f5b725b28221ff3"}, - {file = "websockets-10.2-cp39-cp39-win_amd64.whl", hash = "sha256:1c1f3b18c8162e3b09761d0c6a0305fd642934202541cc511ef972cb9463261e"}, - {file = "websockets-10.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5a38a0175ae82e4a8c4bac29fc01b9ee26d7d5a614e5ee11e7813c68a7d938ce"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e56606842bb24e16e36ae7eb308d866b4249cf0be8f63b212f287eeb76b124"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0f73cb2526d6da268e86977b2c4b58f2195994e53070fe567d5487c6436047e6"}, - {file = "websockets-10.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cd02f36d37e503aca88ab23cc0a1a0e92a263d37acf6331521eb38040dcf77b"}, - {file = "websockets-10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56d48eebe9e39ce0d68701bce3b21df923aa05dcc00f9fd8300de1df31a7c07c"}, - {file = "websockets-10.2.tar.gz", hash = "sha256:8351c3c86b08156337b0e4ece0e3c5ec3e01fcd14e8950996832a23c99416098"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978"}, + {file = "websockets-10.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500"}, + {file = "websockets-10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8"}, + {file = "websockets-10.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f"}, + {file = "websockets-10.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47"}, + {file = "websockets-10.3-cp310-cp310-win32.whl", hash = "sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae"}, + {file = "websockets-10.3-cp310-cp310-win_amd64.whl", hash = "sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079"}, + {file = "websockets-10.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79"}, + {file = "websockets-10.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e"}, + {file = "websockets-10.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6"}, + {file = "websockets-10.3-cp37-cp37m-win32.whl", hash = "sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1"}, + {file = "websockets-10.3-cp37-cp37m-win_amd64.whl", hash = "sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36"}, + {file = "websockets-10.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69"}, + {file = "websockets-10.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c"}, + {file = "websockets-10.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442"}, + {file = "websockets-10.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76"}, + {file = "websockets-10.3-cp38-cp38-win32.whl", hash = "sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559"}, + {file = "websockets-10.3-cp38-cp38-win_amd64.whl", hash = "sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094"}, + {file = "websockets-10.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667"}, + {file = "websockets-10.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680"}, + {file = "websockets-10.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3"}, + {file = "websockets-10.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8"}, + {file = "websockets-10.3-cp39-cp39-win32.whl", hash = "sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582"}, + {file = "websockets-10.3-cp39-cp39-win_amd64.whl", hash = "sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02"}, + {file = "websockets-10.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4"}, + {file = "websockets-10.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755"}, + {file = "websockets-10.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55"}, + {file = "websockets-10.3.tar.gz", hash = "sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4"}, ] whitenoise = [ {file = "whitenoise-6.0.0-py3-none-any.whl", hash = "sha256:5a4aff543ee860fbe40d743e556adf92ccd41b7df45697cae074afdf657056b9"}, diff --git a/pyproject.toml b/pyproject.toml index 23efa74..67fe4c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ psycopg2 = "^2.8.6" numpy = "^1.19.3" scipy = "^1.5.3" dj-database-url = {git = "https://github.com/jacobian/dj-database-url.git"} # commit 8b48e4bd40e28816a5f0ccc65a1a09e7a474de89 -django-compressor = "^3.0" +django-compressor = "^4.0" django-dbbackup = {git = "https://github.com/django-dbbackup/django-dbbackup.git"} # PR #361 django-debug-toolbar = "^3.1.1" django-allauth = "^0.50.0" diff --git a/requirements.txt b/requirements.txt index 6f35395..14e9624 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,7 +26,7 @@ defusedxml==0.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or dj-database-url @ git+https://github.com/jacobian/dj-database-url.git@master django-allauth==0.50.0 django-appconf==1.0.5; python_version >= "3.6" -django-compressor==3.1 +django-compressor==4.0 django-dbbackup @ git+https://github.com/django-dbbackup/django-dbbackup.git@master ; python_version >= "3.6" django-debug-toolbar==3.2.4; python_version >= "3.6" django-ranged-response==0.2.0 @@ -75,13 +75,13 @@ starkbank-ecdsa==2.0.3; python_version >= "2.7" and python_full_version < "3.0.0 twisted-iocpsupport==1.0.2; platform_system == "Windows" and python_full_version >= "3.6.7" and python_version >= "3.6" twisted==21.7.0; python_full_version >= "3.6.7" txaio==22.2.1; python_version >= "3.7" -typing-extensions==4.1.1; python_version >= "3.6" and python_full_version >= "3.6.7" +typing-extensions==4.2.0; python_version >= "3.7" and python_full_version >= "3.6.7" tzdata==2022.1; sys_platform == "win32" and python_version >= "3.8" urllib3==1.26.9; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" uvicorn==0.17.6; python_version >= "3.7" uvloop==0.16.0; sys_platform != "win32" and sys_platform != "cygwin" and platform_python_implementation != "PyPy" and python_version >= "3.7" watchgod==0.8.2; python_version >= "3.7" wcwidth==0.2.5; python_version >= "3.6" -websockets==10.2; python_version >= "3.7" +websockets==10.3; python_version >= "3.7" whitenoise==6.0.0; python_version >= "3.7" zope.interface==5.4.0; python_full_version >= "3.6.7" and python_version >= "3.6" diff --git a/start_live b/start_live index c6f997a..b022c33 100755 --- a/start_live +++ b/start_live @@ -5,9 +5,9 @@ BOLD=$(printf '\033[1m') NORMAL=$(printf '\033[m') if [ "$1" == "dev" ]; then - docker compose --env-file .env.local -f docker-compose.yml -f docker-compose.dev.yml up --force-recreate --build + docker compose -f docker-compose.yml -f docker-compose.dev.yml up --force-recreate --build elif [ "$1" == "prod" ]; then - docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml up --force-recreate --build + docker compose -f docker-compose.yml -f docker-compose.prod.yml up --force-recreate --build else echo -e "Usage: ./start_live ${BLUE}${NORMAL}" echo diff --git a/utils/environment.py b/utils/environment.py index 6e9627b..e7be88d 100644 --- a/utils/environment.py +++ b/utils/environment.py @@ -84,3 +84,12 @@ def pg_isready(dbname=None, host=None, port=None, username=None): return False else: return True + + +def load_secret(label, default=None): + env = getenv(label, default) + if env is not None and env.startswith("/run/secrets/"): + with open(env) as secret_file: + return secret_file.read() + else: + return env diff --git a/utils/management/commands/createsecrets.py b/utils/management/commands/createsecrets.py index 248db75..2abed45 100644 --- a/utils/management/commands/createsecrets.py +++ b/utils/management/commands/createsecrets.py @@ -1,64 +1,61 @@ from django.core.management.base import BaseCommand from django.core.management.utils import get_random_secret_key +from django.conf import settings class Command(BaseCommand): - help = "Outputs a production-ready dotenv file containing secret keys" + help = "Updates Docker secrets with production-ready values" - def add_arguments(self, parser): - parser.add_argument( - "-e", - "--exclude", - action="append", - default=[], - help="A group of environment variables to exclude " - "(use multiple --exclude to exclude multiple groups). " - "Must be one of: ['secret', 'superuser', 'sendgrid', 'postgres'].", - ) - parser.add_argument( - "-o", "--output", help="Specifies file to which the output is written." - ) + def handle(self, **options): + secrets_dir = settings.BASE_DIR / "compose" / "secrets" - def handle(self, *args, **options): - exclude = options["exclude"] + if (settings.BASE_DIR / ".env").exists(): + from sys import stderr, exit - secrets = [] - if "secret" not in exclude: - SECRET_KEY = get_random_secret_key() - secrets.append(f"SECRET_KEY='{SECRET_KEY}'") - secrets.append("") - if "superuser" not in exclude: - DJANGO_SUPERUSER_USERNAME = "live_admin" - DJANGO_SUPERUSER_PASSWORD = "admin-secret" - DJANGO_SUPERUSER_EMAIL = "sumanth@hacktj.org" - secrets.append(f"DJANGO_SUPERUSER_USERNAME='{DJANGO_SUPERUSER_USERNAME}'") - secrets.append(f"DJANGO_SUPERUSER_PASSWORD='{DJANGO_SUPERUSER_PASSWORD}'") - secrets.append(f"DJANGO_SUPERUSER_EMAIL='{DJANGO_SUPERUSER_EMAIL}'") - secrets.append("") - if "sendgrid" not in exclude: - SENDGRID_API_KEY = "SG.KEY" - secrets.append(f"SENDGRID_API_KEY='{SENDGRID_API_KEY}'") - secrets.append("") - if "postgres" not in exclude: - POSTGRES_PASSWORD = get_random_secret_key() - POSTGRES_USER = "live_postgres" - POSTGRES_DB = "hacktj_live" - POSTGRES_INITDB_ARGS = ( - "--auth-host=scram-sha-256 --auth-local=scram-sha-256 " - "--data-checksums" + # Deleting `.env` is not actually necessary for this + # command, but it is necessary for Docker Compose secrets: + print( + "Detected `.env` file. Please delete this file and then re-run this command.", + file=stderr, ) - POSTGRES_HOST_AUTH_METHOD = "scram-sha-256" - secrets.append(f"POSTGRES_PASSWORD='{POSTGRES_PASSWORD}'") - secrets.append(f"POSTGRES_USER='{POSTGRES_USER}'") - secrets.append(f"POSTGRES_DB='{POSTGRES_DB}'") - secrets.append(f"POSTGRES_INITDB_ARGS='{POSTGRES_INITDB_ARGS}'") - secrets.append(f"POSTGRES_HOST_AUTH_METHOD='{POSTGRES_HOST_AUTH_METHOD}'") - secrets.append("") + exit(1) + + prod_secrets_dir = secrets_dir / "production" - secrets_data = "\n".join(secrets).rstrip() + secret_key_file = prod_secrets_dir / "secret_key.txt" + if not secret_key_file.exists(): + with secret_key_file.open(mode="wt") as f: + f.write(get_random_secret_key()) + + django_superuser_username_file = ( + prod_secrets_dir / "django_superuser_username.txt" + ) + if not django_superuser_username_file.exists(): + with django_superuser_username_file.open(mode="wt") as f: + f.write("live_admin") + django_superuser_password_file = ( + prod_secrets_dir / "django_superuser_password.txt" + ) + if not django_superuser_password_file.exists(): + with django_superuser_password_file.open(mode="wt") as f: + # TODO: make it clear that users who run this command should check + # the ./compose/secrets/production/django_superuser_password.txt + # file contents so they know the password. + f.write(get_random_secret_key()) + django_superuser_email_file = prod_secrets_dir / "django_superuser_email.txt" + if not django_superuser_email_file.exists(): + with django_superuser_email_file.open(mode="wt") as f: + f.write("sumanth@hacktj.org") - if options.get("output"): - with open(options["output"], "wt") as output_file: - output_file.write(secrets_data) - else: - self.stdout.write(secrets_data) + postgres_password_file = prod_secrets_dir / "postgres_password.txt" + if not postgres_password_file.exists(): + with postgres_password_file.open(mode="wt") as f: + f.write(get_random_secret_key()) + postgres_user_file = prod_secrets_dir / "postgres_user.txt" + if not postgres_user_file.exists(): + with postgres_user_file.open(mode="wt") as f: + f.write("live_postgres") + postgres_db_file = prod_secrets_dir / "postgres_db.txt" + if not postgres_db_file.exists(): + with postgres_db_file.open(mode="wt") as f: + f.write("hacktj_live")