From d2cf4b948f42b350fce8317e2148bef482c1a69e Mon Sep 17 00:00:00 2001 From: soyouzpanda Date: Fri, 25 Apr 2025 18:08:29 +0200 Subject: [PATCH] lasuite-docs: init at 3.3.0 --- .../lasuite-docs/environment_variables.patch | 109 +++++++++++++++++ pkgs/by-name/la/lasuite-docs/package.nix | 114 ++++++++++++++++++ .../la/lasuite-docs/secure_settings.patch | 36 ++++++ 3 files changed, 259 insertions(+) create mode 100644 pkgs/by-name/la/lasuite-docs/environment_variables.patch create mode 100644 pkgs/by-name/la/lasuite-docs/package.nix create mode 100644 pkgs/by-name/la/lasuite-docs/secure_settings.patch diff --git a/pkgs/by-name/la/lasuite-docs/environment_variables.patch b/pkgs/by-name/la/lasuite-docs/environment_variables.patch new file mode 100644 index 000000000000..618a809a5a7b --- /dev/null +++ b/pkgs/by-name/la/lasuite-docs/environment_variables.patch @@ -0,0 +1,109 @@ +From dd7d54e64bbdb853ff60162908f142cb34034cdd Mon Sep 17 00:00:00 2001 +From: soyouzpanda +Date: Mon, 28 Apr 2025 18:18:39 +0200 +Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8(backend)=20support=20`=5FFILE`=20?= + =?UTF-8?q?environment=20variables=20for=20secrets?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Allow configuration variables that handles secrets, like +`DJANGO_SECRET_KEY` to be able to read from a file which is given +through an environment file. + +For example, if `DJANGO_SECRET_KEY_FILE` is set to +`/var/lib/docs/django-secret-key`, the value of `DJANGO_SECRET_KEY` will +be the content of `/var/lib/docs/django-secret-key`. +--- + src/backend/impress/settings.py | 19 ++++++++++--------- + 1 files changed, 10 insertions(+), 9 deletions(-) + +diff --git a/impress/settings.py b/impress/settings.py +index 571d7052..23c75a98 100755 +--- a/impress/settings.py ++++ b/impress/settings.py +@@ -18,6 +18,7 @@ from django.utils.translation import gettext_lazy as _ + + import sentry_sdk + from configurations import Configuration, values ++from lasuite.configuration.values import SecretFileValue + from sentry_sdk.integrations.django import DjangoIntegration + from sentry_sdk.integrations.logging import ignore_logger + +@@ -65,7 +66,7 @@ class Base(Configuration): + + # Security + ALLOWED_HOSTS = values.ListValue([]) +- SECRET_KEY = values.Value(None) ++ SECRET_KEY = SecretFileValue(None) + SERVER_TO_SERVER_API_TOKENS = values.ListValue([]) + + # Application definition +@@ -84,7 +85,7 @@ class Base(Configuration): + "impress", environ_name="DB_NAME", environ_prefix=None + ), + "USER": values.Value("dinum", environ_name="DB_USER", environ_prefix=None), +- "PASSWORD": values.Value( ++ "PASSWORD": SecretFileValue( + "pass", environ_name="DB_PASSWORD", environ_prefix=None + ), + "HOST": values.Value( +@@ -122,10 +123,10 @@ class Base(Configuration): + AWS_S3_ENDPOINT_URL = values.Value( + environ_name="AWS_S3_ENDPOINT_URL", environ_prefix=None + ) +- AWS_S3_ACCESS_KEY_ID = values.Value( ++ AWS_S3_ACCESS_KEY_ID = SecretFileValue( + environ_name="AWS_S3_ACCESS_KEY_ID", environ_prefix=None + ) +- AWS_S3_SECRET_ACCESS_KEY = values.Value( ++ AWS_S3_SECRET_ACCESS_KEY = SecretFileValue( + environ_name="AWS_S3_SECRET_ACCESS_KEY", environ_prefix=None + ) + AWS_S3_REGION_NAME = values.Value( +@@ -384,7 +385,7 @@ class Base(Configuration): + EMAIL_BRAND_NAME = values.Value(None) + EMAIL_HOST = values.Value(None) + EMAIL_HOST_USER = values.Value(None) +- EMAIL_HOST_PASSWORD = values.Value(None) ++ EMAIL_HOST_PASSWORD = SecretFileValue(None) + EMAIL_LOGO_IMG = values.Value(None) + EMAIL_PORT = values.PositiveIntegerValue(None) + EMAIL_USE_TLS = values.BooleanValue(False) +@@ -407,7 +408,7 @@ class Base(Configuration): + COLLABORATION_API_URL = values.Value( + None, environ_name="COLLABORATION_API_URL", environ_prefix=None + ) +- COLLABORATION_SERVER_SECRET = values.Value( ++ COLLABORATION_SERVER_SECRET = SecretFileValue( + None, environ_name="COLLABORATION_SERVER_SECRET", environ_prefix=None + ) + COLLABORATION_WS_URL = values.Value( +@@ -477,7 +478,7 @@ class Base(Configuration): + OIDC_RP_CLIENT_ID = values.Value( + "impress", environ_name="OIDC_RP_CLIENT_ID", environ_prefix=None + ) +- OIDC_RP_CLIENT_SECRET = values.Value( ++ OIDC_RP_CLIENT_SECRET = SecretFileValue( + None, + environ_name="OIDC_RP_CLIENT_SECRET", + environ_prefix=None, +@@ -592,7 +593,7 @@ class Base(Configuration): + AI_FEATURE_ENABLED = values.BooleanValue( + default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None + ) +- AI_API_KEY = values.Value(None, environ_name="AI_API_KEY", environ_prefix=None) ++ AI_API_KEY = SecretFileValue(None, environ_name="AI_API_KEY", environ_prefix=None) + AI_BASE_URL = values.Value(None, environ_name="AI_BASE_URL", environ_prefix=None) + AI_MODEL = values.Value(None, environ_name="AI_MODEL", environ_prefix=None) + AI_ALLOW_REACH_FROM = values.Value( +@@ -613,7 +614,7 @@ class Base(Configuration): + } + + # Y provider microservice +- Y_PROVIDER_API_KEY = values.Value( ++ Y_PROVIDER_API_KEY = SecretFileValue( + environ_name="Y_PROVIDER_API_KEY", + environ_prefix=None, + ) + diff --git a/pkgs/by-name/la/lasuite-docs/package.nix b/pkgs/by-name/la/lasuite-docs/package.nix new file mode 100644 index 000000000000..03300b635979 --- /dev/null +++ b/pkgs/by-name/la/lasuite-docs/package.nix @@ -0,0 +1,114 @@ +{ + lib, + python3, + fetchFromGitHub, +}: +let + python = python3.override { + self = python3; + packageOverrides = self: super: { + django = super.django_5_2; + }; + }; +in + +python.pkgs.buildPythonApplication rec { + pname = "lasuite-docs"; + version = "3.3.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "suitenumerique"; + repo = "docs"; + tag = "v${version}"; + hash = "sha256-SLTNkK578YhsDtVBS4vH0E/rXx+rXZIyXMhqwr95QEA="; + }; + + sourceRoot = "source/src/backend"; + + patches = [ + # Support for $ENVIRONMENT_VARIABLE_FILE to be able to pass secret files + # See: https://github.com/suitenumerique/docs/pull/912 + ./environment_variables.patch + # Support configuration throught environment variables for SECURE_* + ./secure_settings.patch + ]; + + build-system = with python.pkgs; [ setuptools ]; + + dependencies = with python.pkgs; [ + beautifulsoup4 + boto3 + celery + django + django-configurations + django-cors-headers + django-countries + django-extensions + django-filter + django-lasuite + django-parler + django-redis + django-storages + django-timezone-field + django-treebeard + djangorestframework + drf-spectacular + drf-spectacular-sidecar + dockerflow + easy-thumbnails + factory-boy + gunicorn + jsonschema + lxml + markdown + mozilla-django-oidc + nested-multipart-parser + openai + psycopg + pycrdt + pyjwt + pyopenssl + python-magic + redis + requests + sentry-sdk + whitenoise + ]; + + pythonRelaxDeps = true; + + postBuild = '' + export DATA_DIR=$(pwd)/data + ${python.pythonOnBuildForHost.interpreter} manage.py collectstatic --no-input --clear + ''; + + postInstall = + let + pythonPath = python.pkgs.makePythonPath dependencies; + in + '' + mkdir -p $out/{bin,share} + + cp ./manage.py $out/bin/.manage.py + cp -r data/static $out/share + chmod +x $out/bin/.manage.py + + makeWrapper $out/bin/.manage.py $out/bin/docs \ + --prefix PYTHONPATH : "${pythonPath}" + makeWrapper ${lib.getExe python.pkgs.celery} $out/bin/celery \ + --prefix PYTHONPATH : "${pythonPath}:$out/${python.sitePackages}" + makeWrapper ${lib.getExe python.pkgs.gunicorn} $out/bin/gunicorn \ + --prefix PYTHONPATH : "${pythonPath}:$out/${python.sitePackages}" + ''; + + meta = { + description = "A collaborative note taking, wiki and documentation platform that scales. Built with Django and React. Opensource alternative to Notion or Outline"; + homepage = "https://github.com/suitenumerique/docs"; + changelog = "https://github.com/suitenumerique/docs/blob/${src.tag}/CHANGELOG.md"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ soyouzpanda ]; + mainProgram = "docs"; + platforms = lib.platforms.all; + }; +} diff --git a/pkgs/by-name/la/lasuite-docs/secure_settings.patch b/pkgs/by-name/la/lasuite-docs/secure_settings.patch new file mode 100644 index 000000000000..f679c96b0fc5 --- /dev/null +++ b/pkgs/by-name/la/lasuite-docs/secure_settings.patch @@ -0,0 +1,36 @@ +diff --git a/impress/settings.py b/impress/settings.py +index 9d825095..518aca7f 100755 +--- a/impress/settings.py ++++ b/impress/settings.py +@@ -822,19 +822,24 @@ class Production(Base): + # + # In other cases, you should comment the following line to avoid security issues. + # SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +- SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +- SECURE_HSTS_SECONDS = 60 +- SECURE_HSTS_PRELOAD = True +- SECURE_HSTS_INCLUDE_SUBDOMAINS = True +- SECURE_SSL_REDIRECT = True ++ SECURE_PROXY_SSL_HEADER = values.TupleValue(("HTTP_X_FORWARDED_PROTO", "https"), ++ environ_name="SECURE_PROXY_SSL_HEADER") ++ SECURE_HSTS_SECONDS = values.IntegerValue( ++ 60, environ_name="SECURE_HSTS_SECONDS") ++ SECURE_HSTS_PRELOAD = values.BooleanValue( ++ True, environ_name="SECURE_HSTS_PRELOAD") ++ SECURE_HSTS_INCLUDE_SUBDOMAINS = values.BooleanValue( ++ True, environ_name="SECURE_HSTS_INCLUDE_SUBDOMAINS") ++ SECURE_SSL_REDIRECT = values.BooleanValue( ++ True, environ_name="SECURE_SSL_REDIRECT") + SECURE_REDIRECT_EXEMPT = [ + "^__lbheartbeat__", + "^__heartbeat__", + ] + + # Modern browsers require to have the `secure` attribute on cookies with `Samesite=none` +- CSRF_COOKIE_SECURE = True +- SESSION_COOKIE_SECURE = True ++ CSRF_COOKIE_SECURE = values.BooleanValue(True, environ_name="CSRF_COOKIE_SECURE") ++ SESSION_COOKIE_SECURE = values.BooleanValue(True, environ_name="SESSION_COOKIE_SECURE") + + # Privacy + SECURE_REFERRER_POLICY = "same-origin"