diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..a791fb2c0 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: bin/buildpack_start.sh +postdeploy: python manage.py migrate \ No newline at end of file diff --git a/bin/buildpack_postcompile.sh b/bin/buildpack_postcompile.sh new file mode 100755 index 000000000..c24b9b067 --- /dev/null +++ b/bin/buildpack_postcompile.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit # always exit on error +set -o pipefail # don't ignore exit codes when piping output + +echo "-----> Running post-compile script" + +rm -rf docker docs env.d gitlint src/frontend/apps/e2e +rm -rf src/frontend/apps +rm -rf src/frontend/packages + +# Remove some of the larger packages required by the frontend only +rm -rf src/frontend/node_modules/@next src/frontend/node_modules/next src/frontend/node_modules/react-icons src/frontend/node_modules/@gouvfr-lasuite + +# du -ch | sort -rh | head -n 100 diff --git a/bin/buildpack_postfrontend.sh b/bin/buildpack_postfrontend.sh new file mode 100755 index 000000000..83cb2ab9d --- /dev/null +++ b/bin/buildpack_postfrontend.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit # always exit on error +set -o pipefail # don't ignore exit codes when piping output + +echo "-----> Running post-frontend script" + +# Move the frontend build to the nginx root and clean up +mkdir -p build/ +mv src/frontend/apps/impress/out build/frontend-out + +mv src/backend/* ./ +mv src/nginx/* ./ + +echo "3.13" > .python-version diff --git a/bin/buildpack_start.sh b/bin/buildpack_start.sh new file mode 100755 index 000000000..6ad8f5141 --- /dev/null +++ b/bin/buildpack_start.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Start the Django backend server +gunicorn -b :8000 impress.wsgi:application --log-file - & + +# Start the Y provider service +cd src/frontend/servers/y-provider && PORT=4444 ../../.scalingo/node/bin/node dist/start-server.js & + +# Start the Nginx server +bin/run & + +# if the current shell is killed, also terminate all its children +trap "pkill SIGTERM -P $$" SIGTERM + +# wait for a single child to finish, +wait -n +# then kill all the other tasks +pkill -P $$ diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 737bb3382..05de452b6 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -16,6 +16,7 @@ from django.utils.translation import gettext_lazy as _ +import dj_database_url import sentry_sdk from configurations import Configuration, values from sentry_sdk.integrations.django import DjangoIntegration @@ -74,7 +75,9 @@ class Base(Configuration): # Database DATABASES = { - "default": { + "default": dj_database_url.config() + if os.environ.get("DATABASE_URL") + else { "ENGINE": values.Value( "django.db.backends.postgresql_psycopg2", environ_name="DB_ENGINE", diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index eef304798..33533431b 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "boto3==1.38.18", "Brotli==1.1.0", "celery[redis]==5.5.2", + "dj-database-url==2.3.0", "django-configurations==2.5.1", "django-cors-headers==4.7.0", "django-countries==7.6.1", diff --git a/src/frontend/package.json b/src/frontend/package.json index 3cdc9f600..8f030132b 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -19,6 +19,7 @@ "app:build": "yarn APP_IMPRESS run build", "app:test": "yarn APP_IMPRESS run test", "ci:build": "yarn APP_IMPRESS run build:ci", + "build": "yarn APP_IMPRESS run build && yarn COLLABORATION_SERVER run build", "e2e:test": "yarn APP_E2E run test", "lint": "yarn APP_IMPRESS run lint && yarn APP_E2E run lint && yarn workspace eslint-config-impress run lint && yarn I18N run lint && yarn COLLABORATION_SERVER run lint", "i18n:extract": "yarn I18N run extract-translation", diff --git a/src/nginx/servers.conf.erb b/src/nginx/servers.conf.erb new file mode 100644 index 000000000..36efb1335 --- /dev/null +++ b/src/nginx/servers.conf.erb @@ -0,0 +1,114 @@ +# ERB templated nginx configuration +# see https://doc.scalingo.com/platform/deployment/buildpacks/nginx + +upstream backend_server { + server localhost:8000 fail_timeout=0; +} + +upstream collaboration_server { + server localhost:4444 fail_timeout=0; +} + +server { + + listen <%= ENV["PORT"] %>; + server_name _; + + root /app/build/frontend-out; + + error_page 404 /404.html; + + location /collaboration/api/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://collaboration_server; + } + + location /collaboration/ws/ { + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + + # Set appropriate timeout for WebSocketAdd commentMore actions + proxy_read_timeout 86400; + proxy_send_timeout 86400; + + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://collaboration_server; + } + + # Django rest framework + location ^~ /api/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://backend_server; + } + + # Django admin + location ^~ /admin/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://backend_server; + } + + # Proxy auth for media + location /media/ { + # Auth request configuration + auth_request /media-auth; + auth_request_set $authHeader $upstream_http_authorization; + auth_request_set $authDate $upstream_http_x_amz_date; + auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256; + + # Pass specific headers from the auth response + proxy_set_header Authorization $authHeader; + proxy_set_header X-Amz-Date $authDate; + proxy_set_header X-Amz-Content-SHA256 $authContentSha256; + + # Get resource from Object Storage + proxy_pass <%= ENV["AWS_S3_ENDPOINT_URL"] %>/<%= ENV["AWS_STORAGE_BUCKET_NAME"] %>/; + proxy_set_header Host <%= ENV["AWS_S3_ENDPOINT_URL"].split("://")[1] %>; + + add_header Content-Security-Policy "default-src 'none'" always; + } + + location /media-auth { + proxy_pass http://backend_server/api/v1.0/documents/media-auth/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Original-URL $request_uri; + + # Prevent the body from being passed + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-Method $request_method; + } + + + location / { + try_files $uri index.html $uri/ =404; + } + + location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" { + try_files $uri /docs/[id]/index.html; + } + + location = /404.html { + internal; + } + +} \ No newline at end of file