diff --git a/hive.nix b/hive.nix index f1c103d..8c4f333 100644 --- a/hive.nix +++ b/hive.nix @@ -165,6 +165,7 @@ in imports = [ ./profiles/vm.nix + ./profiles/indico.nix ]; }; } diff --git a/pkgs/flask-multipass/default.nix b/pkgs/flask-multipass/default.nix new file mode 100644 index 0000000..2a6f410 --- /dev/null +++ b/pkgs/flask-multipass/default.nix @@ -0,0 +1,57 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + hatchling, + flask, + blinker, + authlib, + requests, + flask-wtf, + python-ldap, + python3-saml, + sqlalchemy, +}: + +buildPythonPackage rec { + pname = "flask-multipass"; + version = "0.6"; + pyproject = true; + + src = fetchFromGitHub { + owner = "indico"; + repo = "flask-multipass"; + rev = "refs/tags/v${version}"; + hash = "sha256-1SfyCpa1j7a1emhruVLBtXwDr6BisnvUnTA1ioQmGzQ="; + }; + + build-system = [ hatchling ]; + + dependencies = [ + flask + blinker + ]; + + optional-dependencies = { + authlib = [ + authlib + requests + ]; + ldap = [ + flask-wtf + python-ldap + ]; + saml = [ python3-saml ]; + sqlalchemy = [ + sqlalchemy + flask-wtf + ]; + }; + + meta = { + description = "A pluggable solution for multi-backend authentication with Flask"; + homepage = "https://github.com/indico/flask-multipass"; + changelog = "https://github.com/indico/flask-multipass/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/flask-pluginengine/default.nix b/pkgs/flask-pluginengine/default.nix new file mode 100644 index 0000000..a266f0a --- /dev/null +++ b/pkgs/flask-pluginengine/default.nix @@ -0,0 +1,41 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + flask, + jinja2, + blinker, + importlib-metadata, +}: + +buildPythonPackage rec { + pname = "flask-pluginengine"; + version = "0.5"; + pyproject = true; + + src = fetchFromGitHub { + owner = "indico"; + repo = "flask-pluginengine"; + rev = "refs/tags/v${version}"; + hash = "sha256-O41z1GYA4pau6UP2rw4aU4SMr3sXsn4yd+yMvVNwT0k="; + }; + + build-system = [ setuptools ]; + + dependencies = [ + flask + jinja2 + blinker + importlib-metadata + ]; + + pythonImportChecks = [ "flask_pluginengine" ]; + + meta = { + description = "A simple plugin system for Flask applications"; + homepage = "https://github.com/indico/flask-pluginengine"; + changelog = "https://github.com/indico/flask-pluginengine/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/flask-webpackext/default.nix b/pkgs/flask-webpackext/default.nix new file mode 100644 index 0000000..87361d3 --- /dev/null +++ b/pkgs/flask-webpackext/default.nix @@ -0,0 +1,41 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + wheel, + babel, + flask, + pywebpack, +}: + +buildPythonPackage rec { + pname = "flask-webpackext"; + version = "2.0.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "inveniosoftware"; + repo = "flask-webpackext"; + rev = "refs/tags/v${version}"; + hash = "sha256-jGYUsmj52dsu9hR4TEDVt/jX3mt5ForYbhwi2t4DaRw="; + }; + + build-system = [ + setuptools + wheel + babel + ]; + + dependencies = [ + flask + pywebpack + ]; + + meta = { + description = "Webpack integration for Flask"; + homepage = "https://github.com/inveniosoftware/flask-webpackext"; + changelog = "https://github.com/inveniosoftware/flask-webpackext/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/indico-fonts/default.nix b/pkgs/indico-fonts/default.nix new file mode 100644 index 0000000..39fdcb7 --- /dev/null +++ b/pkgs/indico-fonts/default.nix @@ -0,0 +1,31 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, +}: + +buildPythonPackage rec { + pname = "indico-fonts"; + version = "1.2"; + pyproject = true; + + src = fetchFromGitHub { + owner = "indico"; + repo = "indico-fonts"; + rev = "refs/tags/v${version}"; + hash = "sha256-tqpsmthcg9BXxoM/cii+dtXzw0vgu8sek/F1cVNTu+8="; + }; + + build-system = [ setuptools ]; + + meta = { + description = "Indico binary fonts"; + homepage = "https://github.com/indico/indico-fonts"; + license = [ + lib.licenses.ofl + lib.licenses.arphicpl + # TODO indico_fonts/LICENSE-efont + ]; + }; +} diff --git a/pkgs/indico/default.nix b/pkgs/indico/default.nix new file mode 100644 index 0000000..d00560e --- /dev/null +++ b/pkgs/indico/default.nix @@ -0,0 +1,174 @@ +{ + lib, + fetchFromGitHub, + fetchPypi, + python3, +}: + +let + python = python3.override { + self = python; + packageOverrides = self: super: { + celery = super.celery.overridePythonAttrs rec { + optional-dependencies.redis = [ self.redis ]; + }; + # Version 3.0.5 is the last one to support SQLAlchemy 1.4 + flask-multipass = self.callPackage ../flask-multipass { }; + flask-pluginengine = self.callPackage ../flask-pluginengine { }; + flask-sqlalchemy = super.flask-sqlalchemy.overridePythonAttrs rec { + version = "3.0.5"; + src = fetchPypi { + pname = "flask_sqlalchemy"; + inherit version; + hash = "sha256-xXZeWMoUVAG1IQbA9GF4VpJDxdolVWviwjHsxghnxbE="; + }; + }; + flask-webpackext = self.callPackage ../flask-webpackext { }; + indico-fonts = self.callPackage ../indico-fonts { }; + lxml = super.lxml.overridePythonAttrs rec { + optional-dependencies.html5 = [ self.html5lib ]; + }; + pynpm = self.callPackage ../pynpm { }; + pywebpack = self.callPackage ../pywebpack { }; + sentry-sdk = super.sentry-sdk_1; + sqlalchemy = super.sqlalchemy_1_4; + wtforms-dateutil = self.callPackage ../wtforms-dateutil { }; + wtforms-sqlalchemy = self.callPackage ../wtforms-sqlalchemy { }; + }; + }; +in +python.pkgs.buildPythonApplication rec { + pname = "indico"; + version = "3.3.5"; + pyproject = true; + + disabled = python.pkgs.pythonOlder "3.12"; + + src = fetchFromGitHub { + owner = "indico"; + repo = "indico"; + rev = "refs/tags/v${version}"; + hash = "sha256-OX5tqeIjd7Lb5XfvFFKcYb9Dbf5Z9QLXlVTepTpeOMU="; + }; + + build-system = with python.pkgs; [ + hatchling + hatch-requirements-txt + ]; + + patches = [ + ./remove_marshmallow_enum.patch + ]; + + postPatch = '' + # See JoshData/python-email-validator#90 + substituteInPlace requirements.in \ + --replace "email-validator<1.3.0" "email-validator!=1.3.0" + # Botocore doesn't seem to be used anymore + substituteInPlace requirements.in --replace "urllib3<2" "urllib3" + # Seems fixed + substituteInPlace requirements.in --replace "pydyf<0.10" "pydyf" + # Remove transitive dependencies and unnecessary version pins + cp requirements.in requirements.txt + ''; + + dependencies = + with python.pkgs; + [ + alembic + authlib + babel + bcrypt + bleach + blinker + captcha + celery + certifi + click + colorclass + distro + email-validator + feedgen + flask-babel + flask-caching + flask-cors + flask-limiter + flask-marshmallow + flask-migrate + flask-multipass + flask-pluginengine + flask-sqlalchemy + flask-webpackext + flask-wtf + flask + google-auth + hiredis + html2text + icalendar + indico-fonts + ipython + itsdangerous + jinja2 + jsonschema + lxml + markdown + markupsafe + marshmallow-dataclass + marshmallow-oneofschema + marshmallow-sqlalchemy + marshmallow + packaging + pillow + prompt-toolkit + psycopg2 + pycountry + pydyf + pygments + pypdf + python-dateutil + pytz + pywebpack + pyyaml + qrcode + redis + reportlab + requests + sentry-sdk + simplejson + speaklater + sqlalchemy + terminaltables + translitcodec + ua-parser + urllib3 + wallet-py3k + weasyprint + webargs + werkzeug + wtforms + wtforms-dateutil + wtforms-sqlalchemy + xlsxwriter + ] + ++ bleach.optional-dependencies.css + ++ celery.optional-dependencies.redis + ++ qrcode.optional-dependencies.pil + ++ lxml.optional-dependencies.html5 + ++ redis.optional-dependencies.hiredis + ++ sentry-sdk_1.optional-dependencies.flask + ++ sentry-sdk_1.optional-dependencies.celery + ++ sentry-sdk_1.optional-dependencies.sqlalchemy + ++ sentry-sdk_1.optional-dependencies.pure_eval + ++ wtforms.optional-dependencies.email; + + passthru = { + inherit python; + }; + + meta = { + description = "Full-featured conferency lifecycle management and meeting/lecture scheduling tool"; + homepage = "https://getindico.io/"; + changelog = "https://docs.getindico.io/en/stable/changelog/"; + license = lib.licenses.mit; + }; +} diff --git a/pkgs/indico/remove_marshmallow_enum.patch b/pkgs/indico/remove_marshmallow_enum.patch new file mode 100644 index 0000000..ba2fdfd --- /dev/null +++ b/pkgs/indico/remove_marshmallow_enum.patch @@ -0,0 +1,678 @@ +diff --git a/indico/core/marshmallow.py b/indico/core/marshmallow.py +index d3718a5..e51ba0a 100644 +--- a/indico/core/marshmallow.py ++++ b/indico/core/marshmallow.py +@@ -10,7 +10,6 @@ from inspect import getmro + from flask_marshmallow import Marshmallow + from flask_marshmallow.sqla import SQLAlchemyAutoSchemaOpts + from marshmallow import fields, post_dump, post_load, pre_load +-from marshmallow_enum import EnumField + from marshmallow_sqlalchemy import ModelConverter + from marshmallow_sqlalchemy import SQLAlchemyAutoSchema as MSQLASQLAlchemyAutoSchema + from sqlalchemy.orm import ColumnProperty +@@ -33,7 +32,7 @@ class IndicoModelConverter(ModelConverter): + SQLA_TYPE_MAPPING = ModelConverter.SQLA_TYPE_MAPPING.copy() + SQLA_TYPE_MAPPING.update({ + UTCDateTime: fields.DateTime, +- PyIntEnum: EnumField ++ PyIntEnum: fields.Enum + }) + + def _get_field_kwargs_for_property(self, prop): +diff --git a/indico/modules/categories/controllers/display.py b/indico/modules/categories/controllers/display.py +index 00d229c..b2e0707 100644 +--- a/indico/modules/categories/controllers/display.py ++++ b/indico/modules/categories/controllers/display.py +@@ -17,7 +17,6 @@ import dateutil + from dateutil.parser import ParserError + from dateutil.relativedelta import relativedelta + from flask import flash, jsonify, redirect, request, session +-from marshmallow_enum import EnumField + from pytz import utc + from sqlalchemy.orm import joinedload, load_only, selectinload, subqueryload, undefer, undefer_group + from webargs import fields, validate +@@ -575,7 +574,7 @@ class RHCategoryCalendarViewEvents(RHDisplayCategoryBase): + room = auto() + keywords = auto() + +- @use_kwargs({'group_by': EnumField(GroupBy, load_default=GroupBy.category)}, location='query') ++ @use_kwargs({'group_by': fields.Enum(GroupBy, load_default=GroupBy.category)}, location='query') + def _process_args(self, group_by): + RHDisplayCategoryBase._process_args(self) + tz = self.category.display_tzinfo +diff --git a/indico/modules/events/contributions/schemas.py b/indico/modules/events/contributions/schemas.py +index 3ea9d17..0f1df57 100644 +--- a/indico/modules/events/contributions/schemas.py ++++ b/indico/modules/events/contributions/schemas.py +@@ -9,7 +9,6 @@ import hashlib + from operator import attrgetter + + from marshmallow import fields, post_dump +-from marshmallow_enum import EnumField + from marshmallow_sqlalchemy import column2field + + from indico.core.marshmallow import mm +@@ -38,7 +37,7 @@ class ContributionTypeSchema(mm.SQLAlchemyAutoSchema): + + + class ContributionFieldSchema(mm.Schema): +- visibility = EnumField(ContributionFieldVisibility) ++ visibility = fields.Enum(ContributionFieldVisibility) + + class Meta: + model = ContributionField +diff --git a/indico/modules/events/controllers/display.py b/indico/modules/events/controllers/display.py +index e7d4830..eb4aa57 100644 +--- a/indico/modules/events/controllers/display.py ++++ b/indico/modules/events/controllers/display.py +@@ -8,7 +8,6 @@ + from io import BytesIO + + from flask import jsonify, redirect, request, session +-from marshmallow_enum import EnumField + from webargs import fields + + from indico.modules.events.controllers.base import RHDisplayEventBase, RHEventBase +@@ -27,7 +26,7 @@ from indico.web.rh import RHProtected, allow_signed_url + @allow_signed_url + class RHExportEventICAL(RHDisplayEventBase): + @use_kwargs({ +- 'scope': EnumField(CalendarScope, load_default=None), ++ 'scope': fields.Enum(CalendarScope, load_default=None), + 'detail': fields.String(load_default=None), + 'series': fields.Boolean(load_default=False) # Export the full event series + }, location='query') +diff --git a/indico/modules/events/editing/controllers/backend/timeline.py b/indico/modules/events/editing/controllers/backend/timeline.py +index 1088178..4c9a807 100644 +--- a/indico/modules/events/editing/controllers/backend/timeline.py ++++ b/indico/modules/events/editing/controllers/backend/timeline.py +@@ -13,7 +13,6 @@ from zipfile import ZipFile, ZipInfo + + from flask import jsonify, request, session + from marshmallow import EXCLUDE, fields +-from marshmallow_enum import EnumField + from sqlalchemy.orm import joinedload + from werkzeug.exceptions import BadRequest, Conflict, Forbidden, NotFound, ServiceUnavailable + +@@ -248,7 +247,7 @@ class RHConfirmEditableChanges(RHContributionEditableRevisionBase): + return self.editable.can_perform_submitter_actions(session.user) + + @use_kwargs({ +- 'action': EnumField(EditingConfirmationAction, required=True), ++ 'action': fields.Enum(EditingConfirmationAction, required=True), + 'comment': fields.String(load_default='') + }) + def _process(self, action, comment): +@@ -267,7 +266,7 @@ class RHReplaceRevision(RHContributionEditableRevisionBase): + + @use_kwargs({ + 'comment': fields.String(load_default=''), +- 'revision_type': EnumField(RevisionType) ++ 'revision_type': fields.Enum(RevisionType) + }) + def _process(self, comment, revision_type): + args = parser.parse({ +diff --git a/indico/modules/events/editing/schemas.py b/indico/modules/events/editing/schemas.py +index f3c022e..b5da577 100644 +--- a/indico/modules/events/editing/schemas.py ++++ b/indico/modules/events/editing/schemas.py +@@ -10,7 +10,6 @@ from operator import itemgetter + + from markupsafe import escape + from marshmallow import ValidationError, fields, post_dump, validate, validates, validates_schema +-from marshmallow_enum import EnumField + from sqlalchemy import func + + from indico.core.marshmallow import mm +@@ -251,7 +250,7 @@ class EditableBasicSchema(mm.SQLAlchemyAutoSchema): + model = Editable + fields = ('id', 'type', 'state', 'editor', 'timeline_url', 'revision_count', 'tags', 'last_update_dt') + +- state = EnumField(EditableState) ++ state = fields.Enum(EditableState) + editor = fields.Nested(EditingUserSchema) + timeline_url = fields.String() + tags = fields.List(fields.Nested(EditingTagSchema, only=('id', 'code', 'title', 'color')), +@@ -295,7 +294,7 @@ class FilteredEditableSchema(mm.SQLAlchemyAutoSchema): + attribute='contribution.person_links') + contribution_keywords = fields.List(fields.String(), attribute='contribution.keywords') + contribution_session = fields.Nested(SessionBasicSchema, attribute='contribution.session') +- state = EnumField(EditableState) ++ state = fields.Enum(EditableState) + timeline_url = fields.String() + editor = fields.Nested(EditingUserSchema) + can_assign_self = fields.Function(lambda editable, ctx: editable.can_assign_self(ctx.get('user'))) +@@ -318,7 +317,7 @@ class EditingReviewAction(IndicoEnum): + + + class ReviewEditableArgs(mm.Schema): +- action = EnumField(EditingReviewAction, required=True) ++ action = fields.Enum(EditingReviewAction, required=True) + comment = fields.String(load_default='') + + @validates_schema(skip_on_field_errors=True) +@@ -437,7 +436,7 @@ class EditingMenuItemSchema(mm.Schema): + + + class EditableTypeArgs(mm.Schema): +- editable_types = fields.List(EnumField(EditableType), required=True) ++ editable_types = fields.List(fields.Enum(EditableType), required=True) + + + class EditableTypePrincipalsSchema(mm.Schema): +diff --git a/indico/modules/events/notes/controllers.py b/indico/modules/events/notes/controllers.py +index 4e8a17e..c5b3191 100644 +--- a/indico/modules/events/notes/controllers.py ++++ b/indico/modules/events/notes/controllers.py +@@ -6,7 +6,6 @@ + # LICENSE file for more details. + + from flask import jsonify, redirect, request, session +-from marshmallow_enum import EnumField + from webargs import fields + from werkzeug.exceptions import Forbidden, NotFound + +@@ -70,7 +69,7 @@ class RHApiNote(RHManageNoteBase): + return EventNoteSchema().dump(note.current_revision) + + @use_kwargs({ +- 'render_mode': EnumField(RenderMode, load_default=RenderMode.html), ++ 'render_mode': fields.Enum(RenderMode, load_default=RenderMode.html), + 'source': fields.String(required=True), + 'revision_id': fields.Integer(), + }) +diff --git a/indico/modules/events/papers/controllers/api.py b/indico/modules/events/papers/controllers/api.py +index c06042c..183bc7f 100644 +--- a/indico/modules/events/papers/controllers/api.py ++++ b/indico/modules/events/papers/controllers/api.py +@@ -6,7 +6,6 @@ + # LICENSE file for more details. + + from flask import request, session +-from marshmallow_enum import EnumField + from webargs import fields, validate + from werkzeug.exceptions import Forbidden, UnprocessableEntity + +@@ -50,7 +49,7 @@ class RHCreatePaperComment(RHPaperBase): + + @use_kwargs({ + 'comment': fields.String(validate=not_empty), +- 'visibility': EnumField(PaperCommentVisibility, load_default=None) ++ 'visibility': fields.Enum(PaperCommentVisibility, load_default=None) + }) + def _process(self, comment, visibility): + create_comment(self.paper, comment, visibility, session.user) +@@ -82,7 +81,7 @@ class RHCommentActions(RHPaperBase): + + @use_kwargs({ + 'comment': fields.String(validate=not_empty), +- 'visibility': EnumField(PaperCommentVisibility) ++ 'visibility': fields.Enum(PaperCommentVisibility) + }, partial=True) + def _process_PATCH(self, comment=None, visibility=None): + update_comment(self.comment, comment, visibility) +@@ -94,7 +93,7 @@ class RHJudgePaper(RHPaperBase): + return self.paper.can_judge(session.user, check_state=True) + + @use_kwargs({ +- 'action': EnumField(PaperAction, required=True), ++ 'action': fields.Enum(PaperAction, required=True), + 'comment': fields.String() + }) + def _process(self, action, comment): +@@ -122,7 +121,7 @@ class RHSubmitNewRevision(RHPaperBase): + + def _parse_review_args(event, review_type): + args_schema = { +- 'proposed_action': EnumField(PaperAction, required=True), ++ 'proposed_action': fields.Enum(PaperAction, required=True), + 'comment': fields.String(load_default='') + } + +diff --git a/indico/modules/events/papers/schemas.py b/indico/modules/events/papers/schemas.py +index 4b228ab..fd47bcf 100644 +--- a/indico/modules/events/papers/schemas.py ++++ b/indico/modules/events/papers/schemas.py +@@ -7,8 +7,7 @@ + + from markupsafe import escape + from marshmallow import post_dump +-from marshmallow.fields import Boolean, Decimal, Field, Function, Integer, List, Method, Nested, String +-from marshmallow_enum import EnumField ++from marshmallow.fields import Boolean, Decimal, Enum, Field, Function, Integer, List, Method, Nested, String + + from indico.core.marshmallow import mm + from indico.modules.events.contributions.schemas import BasicContributionSchema +@@ -100,7 +99,7 @@ class PaperRevisionSchema(mm.SQLAlchemyAutoSchema): + judge = Nested(BasicUserSchema) + spotlight_file = Nested(PaperFileSchema) + files = List(Nested(PaperFileSchema)) +- state = EnumField(PaperRevisionState) ++ state = Enum(PaperRevisionState) + timeline = PaperRevisionTimelineField() + judgment_comment_html = Function(lambda revision: escape(revision.judgment_comment)) + reviewer_data = Method('_get_reviewer_data') +diff --git a/indico/modules/events/persons/schemas.py b/indico/modules/events/persons/schemas.py +index 9de1947..f122ae1 100644 +--- a/indico/modules/events/persons/schemas.py ++++ b/indico/modules/events/persons/schemas.py +@@ -6,7 +6,6 @@ + # LICENSE file for more details. + + from marshmallow import fields, post_dump, post_load, pre_load +-from marshmallow_enum import EnumField + + from indico.core.marshmallow import mm + from indico.modules.events.models.persons import EventPerson +@@ -24,7 +23,7 @@ class PersonLinkSchema(mm.Schema): + name = fields.String(attribute='display_full_name', dump_only=True) + first_name = fields.String(load_default='') + last_name = fields.String(required=True) +- _title = EnumField(UserTitle, data_key='title') ++ _title = fields.Enum(UserTitle, data_key='title') + affiliation = fields.String(load_default='') + affiliation_link = ModelField(Affiliation, data_key='affiliation_id', load_default=None, load_only=True) + affiliation_id = fields.Integer(load_default=None, dump_only=True) +@@ -100,4 +99,4 @@ class EventPersonUpdateSchema(EventPersonSchema): + class Meta(EventPersonSchema.Meta): + fields = ('title', 'first_name', 'last_name', 'address', 'phone', 'affiliation', 'affiliation_link') + +- title = EnumField(UserTitle) ++ title = fields.Enum(UserTitle) +diff --git a/indico/modules/events/registration/controllers/management/privacy.py b/indico/modules/events/registration/controllers/management/privacy.py +index eabbe3b..4bbd6f0 100644 +--- a/indico/modules/events/registration/controllers/management/privacy.py ++++ b/indico/modules/events/registration/controllers/management/privacy.py +@@ -7,7 +7,7 @@ + + from flask import redirect, session + from flask.helpers import flash +-from marshmallow_enum import EnumField ++from marshmallow import fields + + from indico.core.db import db + from indico.modules.events import EventLogRealm +@@ -64,7 +64,7 @@ class RHRegistrationPrivacy(RHManageRegFormBase): + class RHAPIRegistrationChangeConsent(RHRegistrationFormRegistrationBase): + """Internal API to change registration consent to publish.""" + +- @use_kwargs({'consent_to_publish': EnumField(RegistrationVisibility)}) ++ @use_kwargs({'consent_to_publish': fields.Enum(RegistrationVisibility)}) + def _process_POST(self, consent_to_publish): + update_registration_consent_to_publish(self.registration, consent_to_publish) + return '', 204 +diff --git a/indico/modules/events/registration/util.py b/indico/modules/events/registration/util.py +index c370676..ea03684 100644 +--- a/indico/modules/events/registration/util.py ++++ b/indico/modules/events/registration/util.py +@@ -15,7 +15,6 @@ from operator import attrgetter + + from flask import json, session + from marshmallow import RAISE, ValidationError, fields, validates +-from marshmallow_enum import EnumField + from PIL import Image, ImageOps + from qrcode import QRCode, constants + from sqlalchemy import and_, or_ +@@ -349,7 +348,7 @@ def make_registration_schema( + schema['notify_user'] = fields.Boolean() + schema['override_required'] = fields.Boolean() + elif regform.needs_publish_consent: +- schema['consent_to_publish'] = EnumField(RegistrationVisibility) ++ schema['consent_to_publish'] = fields.Enum(RegistrationVisibility) + + if captcha_required: + schema['captcha'] = CaptchaField() +diff --git a/indico/modules/rb/controllers/backend/bookings.py b/indico/modules/rb/controllers/backend/bookings.py +index 30f95dd..c361b68 100644 +--- a/indico/modules/rb/controllers/backend/bookings.py ++++ b/indico/modules/rb/controllers/backend/bookings.py +@@ -11,7 +11,6 @@ from datetime import date, datetime, time + import dateutil + from flask import jsonify, request, session + from marshmallow import fields, validate +-from marshmallow_enum import EnumField + from sqlalchemy.orm import joinedload + from werkzeug.exceptions import BadRequest, Forbidden, NotFound + +@@ -65,7 +64,7 @@ class RHTimeline(RHRoomBookingBase): + @use_kwargs({ + 'start_dt': fields.DateTime(required=True), + 'end_dt': fields.DateTime(required=True), +- 'repeat_frequency': EnumField(RepeatFrequency, load_default='NEVER'), ++ 'repeat_frequency': fields.Enum(RepeatFrequency, load_default='NEVER'), + 'repeat_interval': fields.Int(load_default=1), + 'recurrence_weekdays': fields.List(fields.Str(validate=validate.OneOf(WEEKDAYS)), load_default=None), + 'skip_conflicts_with': fields.List(fields.Int(), load_default=None), +@@ -311,7 +310,7 @@ class RHBookingEditCalendars(RHBookingBase): + @use_kwargs({ + 'start_dt': fields.DateTime(required=True), + 'end_dt': fields.DateTime(required=True), +- 'repeat_frequency': EnumField(RepeatFrequency, load_default='NEVER'), ++ 'repeat_frequency': fields.Enum(RepeatFrequency, load_default='NEVER'), + 'repeat_interval': fields.Int(load_default=1), + 'recurrence_weekdays': fields.List(fields.Str(validate=validate.OneOf(WEEKDAYS)), load_default=None) + }, location='query') +@@ -395,7 +394,7 @@ class RHMatchingEvents(RHRoomBookingBase): + @use_kwargs({ + 'start_dt': fields.DateTime(), + 'end_dt': fields.DateTime(), +- 'repeat_frequency': EnumField(RepeatFrequency, load_default='NEVER'), ++ 'repeat_frequency': fields.Enum(RepeatFrequency, load_default='NEVER'), + 'repeat_interval': fields.Int(load_default=1), + 'recurrence_weekdays': fields.List(fields.Str(validate=validate.OneOf(WEEKDAYS)), load_default=None) + }, location='query') +diff --git a/indico/modules/rb/controllers/backend/common.py b/indico/modules/rb/controllers/backend/common.py +index 5387c4f..38ecc9e 100644 +--- a/indico/modules/rb/controllers/backend/common.py ++++ b/indico/modules/rb/controllers/backend/common.py +@@ -6,7 +6,6 @@ + # LICENSE file for more details. + + from marshmallow import validate +-from marshmallow_enum import EnumField + from webargs import fields + + from indico.modules.rb.models.reservations import RepeatFrequency +@@ -23,7 +22,7 @@ search_room_args = { + 'division': fields.Str(), + 'start_dt': fields.DateTime(), + 'end_dt': fields.DateTime(), +- 'repeat_frequency': EnumField(RepeatFrequency), ++ 'repeat_frequency': fields.Enum(RepeatFrequency), + 'repeat_interval': fields.Int(load_default=0), + 'recurrence_weekdays': fields.List(fields.Str(validate=validate.OneOf(WEEKDAYS))), + 'building': fields.Str(), +diff --git a/indico/modules/rb/schemas.py b/indico/modules/rb/schemas.py +index d9cafb8..1bc9854 100644 +--- a/indico/modules/rb/schemas.py ++++ b/indico/modules/rb/schemas.py +@@ -11,7 +11,6 @@ from babel.dates import get_timezone + from flask import session + from marshmallow import ValidationError, fields, post_dump, post_load, validate, validates, validates_schema + from marshmallow.fields import Boolean, Date, DateTime, Function, Method, Nested, Number, Pluck, String +-from marshmallow_enum import EnumField + from sqlalchemy import func + + from indico.core.config import config +@@ -79,7 +78,7 @@ class AdminRoomSchema(mm.SQLAlchemyAutoSchema): + class RoomUpdateSchema(RoomSchema): + owner = Principal() + acl_entries = PrincipalPermissionList(RoomPrincipal) +- protection_mode = EnumField(ProtectionMode) ++ protection_mode = fields.Enum(ProtectionMode) + + class Meta(RoomSchema.Meta): + fields = (*RoomSchema.Meta.fields, 'notification_before_days', 'notification_before_days_weekly', 'owner', +@@ -117,7 +116,7 @@ class RoomUpdateArgsSchema(mm.Schema): + max_advance_days = fields.Int(validate=lambda x: x >= 1, allow_none=True) + comments = fields.String() + acl_entries = PrincipalPermissionList(RoomPrincipal) +- protection_mode = EnumField(ProtectionMode) ++ protection_mode = fields.Enum(ProtectionMode) + + + class RoomEquipmentSchema(mm.SQLAlchemyAutoSchema): +@@ -185,11 +184,11 @@ class ReservationUserEventSchema(mm.Schema): + + class ReservationOccurrenceLinkSchema(mm.SQLAlchemyAutoSchema): + id = Number() +- type = EnumField(LinkType, attribute='link_type') ++ type = fields.Enum(LinkType, attribute='link_type') + object = Nested(ReservationLinkedObjectDataSchema, + only=('url', 'title', 'event_title', 'event_url', 'start_dt', 'end_dt')) + start_dt = NaiveDateTime(attribute='reservation_occurrence.start_dt') +- state = EnumField(ReservationOccurrenceState, attribute='reservation_occurrence.state') ++ state = fields.Enum(ReservationOccurrenceState, attribute='reservation_occurrence.state') + + @post_dump(pass_original=True) + def _hide_restricted_object(self, data, link, **kwargs): +@@ -204,7 +203,7 @@ class ReservationOccurrenceLinkSchema(mm.SQLAlchemyAutoSchema): + + class ReservationOccurrenceSchema(mm.SQLAlchemyAutoSchema): + reservation = Nested(ReservationSchema) +- state = EnumField(ReservationOccurrenceState) ++ state = fields.Enum(ReservationOccurrenceState) + start_dt = NaiveDateTime() + end_dt = NaiveDateTime() + +@@ -262,7 +261,7 @@ class ReservationDetailsSchema(mm.SQLAlchemyAutoSchema): + can_edit = Function(lambda booking: booking.can_edit(session.user)) + can_reject = Function(lambda booking: booking.can_reject(session.user)) + permissions = Method('_get_permissions') +- state = EnumField(ReservationState) ++ state = fields.Enum(ReservationState) + is_linked_to_objects = Function(lambda booking: bool(booking.links)) + start_dt = NaiveDateTime() + end_dt = NaiveDateTime() +@@ -302,7 +301,7 @@ class ReservationDetailsSchema(mm.SQLAlchemyAutoSchema): + + class BlockedRoomSchema(mm.SQLAlchemyAutoSchema): + room = Nested(RoomSchema, only=('id', 'name', 'sprite_position', 'full_name')) +- state = EnumField(BlockedRoomState) ++ state = fields.Enum(BlockedRoomState) + + class Meta: + model = BlockedRoom +@@ -398,7 +397,7 @@ class CreateBookingSchema(mm.Schema): + + start_dt = fields.DateTime(required=True) + end_dt = fields.DateTime(required=True) +- repeat_frequency = EnumField(RepeatFrequency, required=True) ++ repeat_frequency = fields.Enum(RepeatFrequency, required=True) + repeat_interval = fields.Int(load_default=0, validate=lambda x: x >= 0) + recurrence_weekdays = fields.List(fields.Str(validate=validate.OneOf(WEEKDAYS))) + room_id = fields.Int(required=True) +@@ -406,7 +405,7 @@ class CreateBookingSchema(mm.Schema): + booking_reason = fields.String(data_key='reason', load_default='') + internal_note = fields.String() + is_prebooking = fields.Bool(load_default=False) +- link_type = EnumField(LinkType) ++ link_type = fields.Enum(LinkType) + link_id = fields.Int() + link_back = fields.Bool(load_default=False) + admin_override_enabled = fields.Bool(load_default=False) +@@ -561,7 +560,7 @@ class SettingsSchema(mm.Schema): + hide_module_if_unauthorized = fields.Bool() + tileserver_url = fields.String(validate=validate.URL(schemes={'http', 'https'}), allow_none=True) + booking_limit = fields.Int(validate=not_empty) +- booking_reason_required = EnumField(BookingReasonRequiredOptions, required=True) ++ booking_reason_required = fields.Enum(BookingReasonRequiredOptions, required=True) + notifications_enabled = fields.Bool() + notification_before_days = fields.Int(validate=validate.Range(min=1, max=30)) + notification_before_days_weekly = fields.Int(validate=validate.Range(min=1, max=30)) +diff --git a/indico/modules/search/controllers.py b/indico/modules/search/controllers.py +index a115a81..7f818d9 100644 +--- a/indico/modules/search/controllers.py ++++ b/indico/modules/search/controllers.py +@@ -7,7 +7,6 @@ + + from flask import jsonify, session + from marshmallow import INCLUDE, fields +-from marshmallow_enum import EnumField + + from indico.modules.categories.controllers.base import RHDisplayCategoryBase + from indico.modules.events.controllers.base import RHDisplayEventBase +@@ -46,7 +45,7 @@ class RHAPISearch(RH): + @use_kwargs({ + 'page': fields.Int(load_default=None), + 'q': fields.String(required=True), +- 'type': fields.List(EnumField(SearchTarget), required=True), ++ 'type': fields.List(fields.Enum(SearchTarget), required=True), + 'admin_override_enabled': fields.Bool( + load_default=False, + validate=validate_with_message(lambda value: session.user and session.user.is_admin, +diff --git a/indico/modules/search/result_schemas.py b/indico/modules/search/result_schemas.py +index 88c274e..96efa43 100644 +--- a/indico/modules/search/result_schemas.py ++++ b/indico/modules/search/result_schemas.py +@@ -6,7 +6,6 @@ + # LICENSE file for more details. + + from marshmallow import EXCLUDE, ValidationError, fields +-from marshmallow_enum import EnumField + from marshmallow_oneofschema import OneOfSchema + + from indico.core.db.sqlalchemy.links import LinkType +@@ -63,7 +62,7 @@ def require_search_target(target): + + + class CategoryResultSchema(ResultSchemaBase): +- type = EnumField(SearchTarget, validate=require_search_target(SearchTarget.category)) ++ type = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.category)) + category_id = fields.Int(required=True) + title = fields.String(required=True) + url = fields.Method('_get_url') +@@ -83,7 +82,7 @@ class LocationResultSchema(mm.Schema): + + class EventResultSchema(ResultSchemaBase): + #: The record type +- type: SearchTarget = EnumField(SearchTarget, validate=require_search_target(SearchTarget.event)) ++ type: SearchTarget = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.event)) + #: The event id + event_id = fields.Int(required=True) + #: The event title +@@ -91,7 +90,7 @@ class EventResultSchema(ResultSchemaBase): + #: The event description + description = fields.String(required=True) + #: The event type +- event_type = EnumField(EventType, required=True) ++ event_type = fields.Enum(EventType, required=True) + #: The event start date time + start_dt = fields.DateTime(required=True) + #: The event end date time +@@ -111,7 +110,7 @@ class EventResultSchema(ResultSchemaBase): + + class ContributionResultSchema(ResultSchemaBase): + #: The record type +- type: SearchTarget = EnumField(SearchTarget, validate=require_search_target(SearchTarget.contribution)) ++ type: SearchTarget = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.contribution)) + #: The contribution id + contribution_id = fields.Int(required=True) + #: The contribution event id +@@ -150,7 +149,7 @@ class ContributionResultSchema(ResultSchemaBase): + + class SubContributionResultSchema(ContributionResultSchema): + #: The record type +- type: SearchTarget = EnumField(SearchTarget, validate=require_search_target(SearchTarget.subcontribution)) ++ type: SearchTarget = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.subcontribution)) + #: The sub-contribution id + subcontribution_id = fields.Int(required=True) + +@@ -188,7 +187,7 @@ def _get_event_path(obj): + + class AttachmentResultSchema(ResultSchemaBase): + #: The record type +- type: SearchTarget = EnumField(SearchTarget, validate=require_search_target(SearchTarget.attachment)) ++ type: SearchTarget = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.attachment)) + #: The attachment id + attachment_id = fields.Int(required=True) + #: The attachment folder id +@@ -206,7 +205,7 @@ class AttachmentResultSchema(ResultSchemaBase): + #: The attachment author + user: PersonSchema = fields.Nested(PersonSchema, load_default=None) + #: The attachment type +- attachment_type: AttachmentType = EnumField(AttachmentType, required=True) ++ attachment_type: AttachmentType = fields.Enum(AttachmentType, required=True) + #: The attachment last modified date time + modified_dt = fields.DateTime(required=True) + # extra fields that are not taken from the data returned by the search engine +@@ -227,7 +226,7 @@ class AttachmentResultSchema(ResultSchemaBase): + + class EventNoteResultSchema(ResultSchemaBase): + #: The record type +- type: SearchTarget = EnumField(SearchTarget, validate=require_search_target(SearchTarget.event_note)) ++ type: SearchTarget = fields.Enum(SearchTarget, validate=require_search_target(SearchTarget.event_note)) + #: The note id + note_id = fields.Int(required=True) + #: The note event id +diff --git a/indico/modules/users/controllers.py b/indico/modules/users/controllers.py +index 5cb5047..52c7a51 100644 +--- a/indico/modules/users/controllers.py ++++ b/indico/modules/users/controllers.py +@@ -15,7 +15,6 @@ from flask import flash, jsonify, redirect, render_template, request, session + from itsdangerous import BadSignature + from markupsafe import Markup, escape + from marshmallow import fields +-from marshmallow_enum import EnumField + from PIL import Image + from sqlalchemy.orm import joinedload, load_only, subqueryload + from sqlalchemy.orm.exc import StaleDataError +@@ -337,7 +336,7 @@ class RHProfilePicturePreview(RHUserBase): + + flash_user_status = False + +- @use_kwargs({'source': EnumField(ProfilePictureSource)}, location='view_args') ++ @use_kwargs({'source': fields.Enum(ProfilePictureSource)}, location='view_args') + def _process(self, source): + if source == ProfilePictureSource.standard: + first_name = self.user.first_name[0].upper() if self.user.first_name else '' +@@ -374,7 +373,7 @@ class RHSaveProfilePicture(RHUserBase): + """Update the user's profile picture.""" + + @use_kwargs({ +- 'source': EnumField(ProfilePictureSource, required=True) ++ 'source': fields.Enum(ProfilePictureSource, required=True) + }) + def _process(self, source): + self.user.picture_source = source +@@ -885,7 +884,7 @@ class RHRejectRegistrationRequest(RHRegistrationRequestBase): + class UserSearchResultSchema(mm.SQLAlchemyAutoSchema): + affiliation_id = fields.Integer(attribute='_affiliation.affiliation_id') + affiliation_meta = fields.Nested(AffiliationSchema, attribute='_affiliation.affiliation_link') +- title = EnumField(UserTitle, attribute='_title') ++ title = fields.Enum(UserTitle, attribute='_title') + + class Meta: + model = User +diff --git a/indico/util/marshmallow.py b/indico/util/marshmallow.py +index fed7293..b331b4a 100644 +--- a/indico/util/marshmallow.py ++++ b/indico/util/marshmallow.py +@@ -14,7 +14,6 @@ import yaml + from dateutil import parser, relativedelta + from marshmallow import Schema, ValidationError, fields + from marshmallow.utils import from_iso_datetime +-from marshmallow_enum import EnumField + from pytz import timezone + from speaklater import _LazyString + from sqlalchemy import inspect +@@ -500,7 +499,7 @@ class LowercaseString(fields.String): + return super()._deserialize(value, attr, data, **kwargs).lower() + + +-class NoneValueEnumField(EnumField): ++class NoneValueEnumField(fields.Enum): + """ + Like the normal EnumField, but when receiving a None value, + this is mapped to a specific enum member. +diff --git a/requirements.in b/requirements.in +index 54be3ee..fd12cfa 100644 +--- a/requirements.in ++++ b/requirements.in +@@ -36,7 +36,6 @@ lxml[html5] + markdown + markupsafe + marshmallow-dataclass +-marshmallow-enum + marshmallow-oneofschema + marshmallow-sqlalchemy + marshmallow +diff --git a/requirements.txt b/requirements.txt +index e1112a7814..53c2d521de 100644 +--- a/requirements.txt ++++ b/requirements.txt +@@ -211,14 +211,11 @@ marshmallow==3.22.0 + # -r requirements.in + # flask-marshmallow + # marshmallow-dataclass +- # marshmallow-enum + # marshmallow-oneofschema + # marshmallow-sqlalchemy + # webargs + marshmallow-dataclass==8.7.0 + # via -r requirements.in +-marshmallow-enum==1.5.1 +- # via -r requirements.in + marshmallow-oneofschema==3.1.1 + # via -r requirements.in + marshmallow-sqlalchemy==1.1.0 \ No newline at end of file diff --git a/pkgs/pynpm/default.nix b/pkgs/pynpm/default.nix new file mode 100644 index 0000000..df945a0 --- /dev/null +++ b/pkgs/pynpm/default.nix @@ -0,0 +1,34 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + wheel, + babel, +}: + +buildPythonPackage rec { + pname = "pynpm"; + version = "0.2.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "inveniosoftware"; + repo = "pynpm"; + rev = "refs/tags/v${version}"; + hash = "sha256-GjfylyxPZrRGCLuNZejmTdQGz5JugKHYpVix8WF/8QK="; + }; + + build-system = [ + setuptools + wheel + babel + ]; + + meta = { + description = "Python interface to your NPM and package.json"; + homepage = "https://github.com/inveniosoftware/pynpm"; + changelog = "https://github.com/inveniosoftware/pynpm/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/pywebpack/default.nix b/pkgs/pywebpack/default.nix new file mode 100644 index 0000000..b4b2039 --- /dev/null +++ b/pkgs/pywebpack/default.nix @@ -0,0 +1,41 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + wheel, + babel, + importlib-metadata, + pynpm, +}: + +buildPythonPackage rec { + pname = "pywebpack"; + version = "2.1.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "inveniosoftware"; + repo = "pywebpack"; + rev = "refs/tags/v${version}"; + hash = "sha256-6hK/k+26Iqb6BE/pv2iFFg5OH6247AAGpvOV6dDRKRT="; + }; + + build-system = [ + setuptools + wheel + babel + ]; + + dependencies = [ + importlib-metadata + pynpm + ]; + + meta = { + description = "Webpack integration layer for Python"; + homepage = "https://github.com/inveniosoftware/pywebpack"; + changelog = "https://github.com/inveniosoftware/pywebpack/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/wtforms-dateutil/default.nix b/pkgs/wtforms-dateutil/default.nix new file mode 100644 index 0000000..1e50f4c --- /dev/null +++ b/pkgs/wtforms-dateutil/default.nix @@ -0,0 +1,36 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + wtforms, + python-dateutil, +}: + +buildPythonPackage rec { + pname = "wtforms-dateutil"; + version = "0.1.0"; + pyproject = true; + + src = fetchFromGitHub { + owner = "wtforms"; + repo = "wtforms-dateutil"; + rev = "refs/tags/${version}"; + hash = "sha256-k1DZqf2FgyupNvRIIrPLwxbTP5MteCnvASCkpCZRyAE="; + }; + + build-system = [ setuptools ]; + + dependencies = [ + wtforms + python-dateutil + ]; + + pythonImportChecks = [ "wtforms_dateutil" ]; + + meta = { + description = "WTForms fields using dateutil"; + homepage = "https://github.com/pallets-eco/wtforms-dateutil"; + license = lib.licenses.bsd3; + }; +} diff --git a/pkgs/wtforms-sqlalchemy/default.nix b/pkgs/wtforms-sqlalchemy/default.nix new file mode 100644 index 0000000..bf592d1 --- /dev/null +++ b/pkgs/wtforms-sqlalchemy/default.nix @@ -0,0 +1,37 @@ +{ + lib, + fetchFromGitHub, + buildPythonPackage, + setuptools, + sqlalchemy, + wtforms, +}: + +buildPythonPackage rec { + pname = "wtforms-sqlalchemy"; + version = "0.4.1"; + pyproject = true; + + src = fetchFromGitHub { + owner = "wtforms"; + repo = "wtforms-sqlalchemy"; + rev = "refs/tags/${version}"; + hash = "sha256-uR09LYfcyre+AC2TTEIhpjSI7y4Yo0GJ20smkzo5PRY="; + }; + + build-system = [ setuptools ]; + + dependencies = [ + sqlalchemy + wtforms + ]; + + pythonImportsCheck = [ "wtforms_sqlalchemy" ]; + + meta = { + description = "WTForms integration for SQLAlchemy"; + homepage = "https://github.com/wtforms/wtforms-sqlalchemy"; + changelog = "https://github.com/wtforms/wtforms-sqlalchemy/blob/${version}/CHANGES.rst"; + license = lib.licenses.bsd3; + }; +} diff --git a/profiles/indico.nix b/profiles/indico.nix new file mode 100644 index 0000000..8f6350e --- /dev/null +++ b/profiles/indico.nix @@ -0,0 +1,116 @@ +{ config, lib, pkgs, python3, ... }: +let + # cfg = config.services.indico; + # pythonFmt = pkgs.formats.pythonVars { }; + indico = pkgs.callPackage ../pkgs/indico { }; + pythonEnv = indico.python.withPackages (ps: [ + indico + # (ps.toPythonModule indico) + ps.gunicorn + ]); +in +{ + # TODO cProfile; indico is *very* slow (~30s just to print the help) + + # + sudo indico + environment.systemPackages = [ indico ]; + + services.redis.servers.indico.enable = true; + + systemd.services.indico-web = { + description = "Indico web service"; + after = [ + "network.target" + "redis-indico.service" + "postgresql.service" + ]; + wantedBy = [ "multi-user.target" ]; + # TODO migrations + serviceConfig = { + User = "indico"; + Group = "indico"; + ExecStart = "${lib.getExe' pythonEnv "gunicorn"} --bind unix:/run/indico/indico.sock --name=indico indico.wsgi"; + Restart = "on-failure"; + }; + enable = true; + }; + + systemd.sockets.gunicorn-web = { + socketConfig = { + ListenStream = "/run/indico/indico.sock"; + SocketUser = "nginx"; + }; + enable = true; + }; + # preStart = '' + # echo "create extension if not exists pg_trgm" | runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra + # '' + + services.postgresql = { + enable = true; + package = pkgs.postgresql_16; + ensureDatabases = [ "indico" ]; + ensureUsers = [ + { + name = "indico"; + ensureDBOwnership = true; + } + ]; + }; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services.nginx = let + indicoBaseDir = "/tmp"; + in { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + upstreams.indico.servers."unix:/run/indico/indico.sock" = { }; + virtualHosts."events.federez.net" = { + enableACME = true; + forceSSL = true; + locations = { + "/.xsf/indico/" = { + alias = "${indicoBaseDir}/"; + extraConfig = '' + internal; + ''; + }; + # Order? + too lax? + "~ ^/(images|fonts)(.*)/(.+?)(__v[0-9a-f]+)?\\.([^.]+)$" = { + alias = "${indicoBaseDir}/web/static/$1$2/$3.$5"; + extraConfig = '' + access_log off; + ''; + }; + "~ ^/(css|dist|images|fonts)/(.*)$" = { + alias = "${indicoBaseDir}/web/static/$1/$2"; + extraConfig = '' + access_log off; + ''; + }; + "= /robots.txt" = { + alias = "${indicoBaseDir}/web/static/robots.txt"; + extraConfig = '' + access_log off; + ''; + "/" = { + proxyPass = "http://indico"; + extraConfig = '' + client_max_body_size 1G; + ''; + }; + }; + }; + }; + + users.users.indico = { + isSystemUser = true; + group = "indico"; + }; + + users.groups.indico = {}; +} diff --git a/secrets/discourse-key-base.age b/secrets/discourse-key-base.age index ff836d1..04928f7 100644 --- a/secrets/discourse-key-base.age +++ b/secrets/discourse-key-base.age @@ -1,33 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 oDAQrw NFtzIriHmvcPmluvbL9yV5fAFKoBWcuY4M3Qy1PogkU -KxRzRwZhrvVxvvRkuVsumYRbUDXsYecvmvKR8osv4/s +-> ssh-ed25519 oDAQrw SBbVb1mkK/vYXtTNGbb7l0sNuusSObbrp6ft41xUHCo +7BokqAWzTauGghPtytuJ/GN1qD+1kZAHGbiKrb4yc2U -> ssh-rsa krWCLQ -e6/eduQLNkjdwInZNonT6xtrRAtlt8UJqrTvUajsfQzR+AcFfcMGuOdzwRIAdbMq -lC8mICFn+9RwwA1360CFSGSZOBrhWnY5Ov9KPsXGOYiup4/fDwk6NJxurnTSJVrb -EUyBQoeds9ZnHlQ9Jq5tjnzVo0gIIm1ixtOq9XnAos4tiDzviZgjimJRZL7zW7Zd -4FQBwXSn8rTT7/gBM4oDbDmBcrbAf9YcP9Wspsye1XgTG/KyqRT75VAJu2l7fUnU -DIlqmQbPPkSIO4MURPXpAg8S3tXAC6fs69s77dq9+NfbvK0TH7blaJYcJiW7ogYY -RTwOG61y78IV55Ze9/9M6A --> ssh-ed25519 /vwQcQ DKJKkbb0RO1jfSBH3KR0OGMfj0ZJQYVz7GDUMP/bJwE -vzNWlrkN3UYst11R3opQSdz/pnd48gWmsv3jv/g+3+A --> ssh-ed25519 0R97PA i+He+K6aBSItKmvA/2rvtS4IfqBK2rOYOBhTURMPqQ4 -k+FuDsFbS1MNT5vEzYSny80FBU+L+OdxMGEAT3e/VP0 +isST86UT1UMMOKiaTWaREFMn22H1MPDbLbuIeZzBhkTh0GnzmKKCxZV7+wPX4Woc +mq9FtkQBcbawrV20ZafF4bKqBl2ByuDmqe+VVWLchrR0DdkspsEBBHIw42rG79ZU +x+nNXnsDAFMdLRBEcNZSBVoGd1KhTuyoOM91dJ1IueJx3j8SA5JNCIG1Zf8vJKqX +WuZtjQiI949wzfU0WX+T5hT0uMzuzeIJ993vUJ2+2twN+KQnq0JYMTG26g40tSH+ +uAENRS6dwQep5BI5nnx1yKli3zX2Dc+4MLXiglKCxkyZ4sjYtfzMnlaAfpahyxVm +8i37qYGdyyxLPRWxS3SMUQ +-> ssh-ed25519 /vwQcQ Ig4WFFJ4Ex6cYghg14FJJfskUKyI5gWJwd5vn0XmDH8 +uHwxOnD8aVeG8YwiMhcj2BB6q3o+CfM6C7GP3jcXjyo +-> ssh-ed25519 0R97PA UvK0BtCJBVNnc2rcpz49iwdqmIt31aAXVFvTZw6/Fyk +fcZquflamVQnWRQVL+NsUwiQaJs9Qzi+Va5wrg0pa1k -> ssh-rsa jL+Elw -pex+P4zsbQhndeUZC/DsmRIYLaclvAMR75NxwQIhlzXTvARWfrClhTXmD0cmA2QM -hMvtDADwW/RRkFZuVKY/u6dA4/yZdua8by1RITrnLVAYDqr7H2u7bCozESJChJJw -XsNglDQ2Zkw9le4EjX0oO7hyt5eM07DYnq3nmFuqVUzWh1qvxOtcELkOE/fi0AwJ -RvrGbM27w0V6N7BYAefBbVc+OFbNg2ai0WECxlL4HXR9BgmVYGybYR+11Bu4scCM -mS9CKbW4UtVhK+tPVEgO3gymwv99PYFwcwvdGdyAAp9yVgjbKZt9Lm6J/eNpj/82 -YMYW6ZBK0jdtT4YSNu/88EXoQghoOyCEbFAYXoHiaNCiFxcKOOdY65B1SATi1Cuh -Qqo3Ejfx7waS3iArPFQFzS4X5ZKtj3GaHXKQkziD7GmP9cjGWt7S8Wh9pi/G6Bx1 -vGFhdfwtd+pGlbhrXd5jU6+UnVGtPBmgN8F/CpvqII4HN62Ds+npUs31uFGW1lzl -b4q+ij3s/Obt7HHCAhywXfnTDZ15WDcU8cWfSlsoTd63sc8INYXvDzFX46kax0E+ -yqGoFzYuke0FJxUf/tll4wv5aY9RcgAoHsDP7CL8nB1556NwVYFn8AujY87IiNAw -G/WSn9URjTgxW1YhiIvJ5L3mp/DjAZiakCU/85KEf08 --> ssh-ed25519 jIXfPA pPEvleLgX5FDDHbWlq6Szu61VMt4W7e3HrM5bYQA4QU -Ag8v967xi0AEbg8fiYAAmA36rkdZETCb3q/veoRdEbA --> ssh-ed25519 um7xWA gkxkaZftmupdeK1fsi7v59wAsM/Usc8SXjmxRkoBjmM -cjiizU43+Fed3vFqOpoePhicklpsSoAhEHZhHlkXr5Q ---- Xw/OABODZr4PGKpuNi03kdUptlyHF8hEK3+KtDulu7U -qk:hMq& eZI -qI[3v|DhQkbS|Sx"մP숸pEhưXU ssh-ed25519 jIXfPA HCM4xJkLVRfAvBzKyS5OZsbu91HPeZgx8sbwUP58LiY +GqJebsL5u0W66xU+cmXHbn7pYo7m//LggPIc3pWdJOQ +-> ssh-ed25519 um7xWA CCztuWK8B5skJS0xkpyo1cIci512Pmsi4Xp3M6Kf9DA +NUEcXszN91ycADY9oGKTGFWnCzv8ZIfEQ3zdkqm6/sg +--- eW8lDje70BhCexL17jXBtSA2X/jCnVwZUvackCd1bd0 +ޛy|L +GQ̎A-2ω}xh*qLp@~Ek._s&c) m~;E|t|oZ֦" \ No newline at end of file diff --git a/secrets/discourse-mail-password.age b/secrets/discourse-mail-password.age index 0810826..ff6cbb9 100644 --- a/secrets/discourse-mail-password.age +++ b/secrets/discourse-mail-password.age @@ -1,32 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 oDAQrw +aORlGGz/jqX0t4opGM5vMTgBKtAdL/z1KxfA4C43To -MNbp/PaSnpvyfZtmWWD6HYG3yHh2uWoXDY8V1Ft7+ac +-> ssh-ed25519 oDAQrw QeSmwUu/CvV+1LIgeeGEW6PFgOESwqTe1o7Ack1PWSs +Z4a8OsJAYdnJfG5IZq/ktMD9F3pfN3cjim8A2nZM5a4 -> ssh-rsa krWCLQ -2Px6I6WuRr10mMWlcUCAbnm6dyQa2hklmnwXtNPWXmKNPzHC6CGoye5dnOvkNKGN -5ekf2mK2ywzE2FQFVYOtY3Ss/60I4OTXmxNJ3qrCAU2z+f+53nAegguc3tB4Xvh8 -tQVQICaX1oVU/PD2SFX8QcTBORF2+Vc0Nd6fbuGl3dhSeESg7JLj5oaBCsthZD2J -U1ehTDS/+t6nogt1BdNZK10yFXRGk44jDMADMvvUPkIEW7mbvTFfMtXNhbemOn+X -oIXt8DNwzn3tF1IdOJ+vjtcU3gqYQYZVFKJV1DtQSA7i46Dl0J0+12+8/zKMX+yu -NKcaa/L1tz0lPX6ZeXhoOg --> ssh-ed25519 /vwQcQ QoEbQ1IA4EKR97Fqa/NQN9RDUEubeZbvxMcd0ZFhwyU -OAFrGGXCxDFcsalinhG3JcXUj+RJawl6UnSgZkBwBrk --> ssh-ed25519 0R97PA VkKhx5oVdrFjjSXwreIiGnH0nZEqY5Ls4OSpgpDn0jI -FC9SN+Woh6QBWV5r5TjtjFSp8mqw3LxgFJCQPjD8oss +GnsaM6qYR58TY9xfDvFOD9On6jHuU6M5ku5hQExsdGFXkp+pultLC4d31iO/9YAw +NC+DjYRfQRgiVl7Ik7l5EbsoJitBsyJqNm6KckLgMr+m36i2l7myrUCXvkj29WPY +s2uRmlo4Ai6pthu2nI7dJJ0Va/Z3WtXOb+q2QIapbB/Jb3mcvkVbKkJNnVWpZjri +pafvy1ZlbWtOIHUHfLCyBo3R2sDQUFR8LL5Ww93CzjbiyfBVe0lgBvJJJUoZ+7qj +ZPykqASPNsKW+IQXb90BZlpf9JRi9l8VawZ1OCasWZ8e7UBvxMp/mpcZ9mUWDE+g +uvcAaBjx0ZHsN1u+k3Sx1A +-> ssh-ed25519 /vwQcQ jCxoSbDPpFe7eHJEX3rov05XWPkWDZvIRbiWhvJmsAI ++ivctO51LaGMF9PJHGhwLRNXXxq8VZSfnbjHIU6lE4M +-> ssh-ed25519 0R97PA 0GSdrtGAYks1eSMQXispNZTaa+UIQbP0eAzRP/aoXS4 +MNmpUVS/D5AxNaKfvxlqd5WW2+0cHjDo7/74vCzPDMU -> ssh-rsa jL+Elw -UGQRNdtkdNbRVcoimpZsgBAGp95mtHwJ8V4CwlL82GqdIGM3LQDrCtPgFTC1C4Zx -lrgVaT/xi4WhRkkHCd0ZTzJ5i9NY9H/gnDaXKeKcTcQezH1yeMBLns92D2z7u0+1 -DyTzu8ZzZLvaej2b7gLU6u2BMC10AnBYP0LFuZlE3ndnr7ro9flKPb5A+IRbX339 -xkNlIOsTBdSfJJ27LKZVIOUS2Pxg1Kos9Wtbg1QsgeikdrA++QD0GhrRd+X4sFrf -Xfjq12XDakCfLmvi9QLJ2hy4X1glCO1lsBocDEaa2dGQMa2yVQi72z/92w83g98C -UO25VkpKwdGFBf8PfhKeEkdSQEJUNOe3mlTEvtpr5S3BM+//fSFHlaS1UMEeamC2 -OrcvTwdby9f4l0++9dEuPcgQYvMzbUndwFn2HI7a8fGSIF6iXeVdrl5zuR3Wgugr -ksMiQ6IyItrv/XlR5945dTxragWIwDeNJFE5EEYU3F7ryhT1FMKUWlt3lDazVuuj -PoLjUnXNhi7I+3XZ22e5P2BW7UmMbsPg3M9l+u+1U1vk/o28cZxrWV6VW4MB1Aiv -TXFXZSl123ag32PdKZ/dV7p4VC4hXEqyLA7qIrE9rFduEI5WQHqd1iL/pGxEOnuM -AOe/acgmB1hhVLCAwCCpERWyC1+Bn7UWRjrPKCLfqFc --> ssh-ed25519 jIXfPA NaF9Sg7UkShfSzE59iFwKF4WMsc0xtAejl+20EQBNzc -4CJqeGTxwcG0ObXeWATVKQkqlyaKZEaHFfGBOc6Xpok --> ssh-ed25519 um7xWA rlZ0xF57VNq1KHijaV9csD+Sq6cfp5DM9k1uuVOhLyY -7uYk1RIET7bkdXTHJdocqOiEtc6vqY4iOBB4cMJ7CyQ ---- FiH1HnZbKHvI9kclq8TrOFlQN6hvNd9M0mxdqjYM5gw - kj p [tϖU9dcڗNE#c|ۃ䋷`GWMy v$d \ No newline at end of file +LWL7LZ+16Bnk2o/5/2QJq9UHqdUBXndU/lx+UqGSemcEJqbuikWEq5Sv9jTgME2U +mQYMr10vGxyDo0mSB9pbYGB5fWlAi61UutQKJuuwHgETF56fVy7+fsRmtokIcG7C +dF2ooUsx5/Ul2V0o2v4KieOZhU4KvhPwHVUdP6SkWCtn5kxrmJkbOjOrRNOVZffz +yOuViZlLlFrtzqtuwBi6uHhoz5x0t+m/v1JM1/zHFI5I3JPEpHvRfVKp7K2dOT2T +nr7UotsoGD2+nqrSp4YVgNdwy6EgjbVKGKF2rDr1hcUyMV+L8vRmAxQhfAWJxPYh +jOgHWIWOCLQX/6MrBB4+gvyNvrZUw6nvDwbMiMlVn6MS6H395Sh42PtiCnx27pc4 +xIGB4Zaq+38SnkpjqLVwSUak7rC0FHHu6nTUIQPIktmz8C1BWpNpTL4mtuT30U45 +MVKB1taKk87XVpqzxOgQVHa3CZzjANm8cpip4yWkcEskhv+3OZjeZNgkUHHR1KGE +suZxJJTrhhabV1N69i5LqmXgr4BZcJEkkdoJ4bFUaPttwkm6aUDpdSJbRowyzDbX +DWzmG/P1IpTdRw/7lKR7WUDMjsUmdMwCWpwlNetHM9t9aS62GBT2JKtTWfAPwdWw +VeGpq5o2R2EBW7ioVO3P14Mk3duD/PB8H8xr+gEczbA +-> ssh-ed25519 jIXfPA UhMaLIx8smNo4NoGkyh7ODvqOqhql4bI+vZsz8WKugE +d1PVZiDWykjx86YDlChW1BrLU/NHbHRfnnJ+m/eCIGQ +-> ssh-ed25519 um7xWA rjgDG4Rpaq0R54at3sc8TSUM1ipBAUq5Zl/di7i7jFc +G8XdosKSH6xxMMWFD5DbtyDdgNvfegbw1zEmTMI/TfU +--- Unyyf+PuoDOyqfNttxSNFPJrbmkLATNL03J0DbR9AxU +~bɸ@;ٟ|+;C-a!3L>k +34lqMv]P-Y9 \ No newline at end of file diff --git a/secrets/matrix-shared-secret.age b/secrets/matrix-shared-secret.age index 3fe8225..3c8acce 100644 Binary files a/secrets/matrix-shared-secret.age and b/secrets/matrix-shared-secret.age differ diff --git a/secrets/mautrix-telegram.age b/secrets/mautrix-telegram.age index 4cbe594..bc510f2 100644 --- a/secrets/mautrix-telegram.age +++ b/secrets/mautrix-telegram.age @@ -1,34 +1,33 @@ age-encryption.org/v1 --> ssh-ed25519 GxF6ZA pCpuN1PDIOfZ4ZewmIv4YWNAbWy3FHQsvMumPHpGOjw -nGgBgGw+65u7+tASWpfk30cc2TzgVJGFdDz6Ar/R+5s +-> ssh-ed25519 GxF6ZA y3IlZBxYfMnMpuWDjVDdT1zE6+bDWyl2pbxuUvwXbEI +N4VsbsnYD2b0TqSE1P2DzuaKT6gnYOjCA/I6USV5Ulc -> ssh-rsa krWCLQ -2oFukVzQIv5zXfd2pK3XwE8xHLCcV7+oORMdz+pTOry9d0u6VQhP5+aKd26/d9q3 -/WL8AszBl9d6czi99QKlliGyjw8K2vl3jOYZCTUJj25bBUsL0wt3Qok26Ywmgg2A -zTWT4AZqgdoQ886kiZFLEePAhVaJ1OOY4ucPkFynKRigqv4gULzwEQQDogLeY2sk -FuhJQlK6nhjakro4rfQxWVnciuPUK0kV0ULGZXrxN/vrpK9zV1CdBnjgjtehMfRA -qzf9FxMyxS/5RV+oCeFWS0jeEOoLun2lwUH8/rGlMeyU1Jq23q07bozWsUitcM2E -IfFMhX2QT/WzmSraBIzs7g --> ssh-ed25519 /vwQcQ OihcemSx1H5D32fB6XReME+z01e5f2wbEsjY4ou7Kgs -2zyndfT7gXpYmQzMmgXLx+1L0A6XftTGbDqfjeMAoaU --> ssh-ed25519 0R97PA OkPY1xsNp3YqSkuo5J+RqjIkBTua9H0CHunZaYvqfQo -8DFe5TUziFk9rbXa8nodX7rE62mh7DicWCJzEpLn2jE +MXO3xn7SQrbcha9SUyaujaAQH2GTfdsb2Ec42QC5B+BCiFEte48mHTzMUZXDiOF/ +00/f4iVLEx8WLRp7YU5Y7ipigGdN+1r3V+ZRLlm7JzoAJ+fFJmqd9ErIzy+trdjC +D3LtNsxwBqHAtXsaq42uF5oV08xvqmrfM6t4YywKsaENSWeRIglSY/ZwzyRugrTr +DmMHnL9PTYr0cnF1vb7hbbN+l01NfCc7MaLKLWA5i3kzuhzKAx48RhCf3eay/6/r +t4bcEAuvj24gaB0bU6Lom65PH/FL69bBtjrFWrPPQIbX88+z+md+nb8++k9NSTD7 +PeavB+zvA+PkXJMiTpVO8Q +-> ssh-ed25519 /vwQcQ Uvp34DlCMLtalncf41gr4qJL2tySq52jeM4K1DBPCSY +yIJfRyIlZ7ShYdTDgebYriHVySvqvuPZoNKATDceL4k +-> ssh-ed25519 0R97PA pOecxvmM8Xk2T8oEhugDCB152QYs0IPBHyDDrRFbySA +kuJF7YejPboLWTAc1oRIFS6gp/pe7gyuPIfsH49XLNc -> ssh-rsa jL+Elw -sJTALMXdOMyhtL8DD8Qyq587PQGyH/hTrMQ32pLbYnVwfX6wKvEBR7lyPMN3jLbk -56WhA9X8Op0zOCzJd1DldKZeGMqUK2fYcjv1JrhTlE0LfS7Y3W4zCEadVr6pbkXo -jc98q3RNdD6V4RqrdtndienJ9zM+vQnyxmkVZOl+z+yi/y7ceLUZcbVBChMTh/Ok -PiS60pk0NA9DFIds+IKVeKGA26iq0s2cjg0AUaicyV1LK4TF3LdLf4aanHt5QWE9 -ykQbtQLpp8DQifBNEl80YBg62YVz8f5B3/zBMjZaxIKIKXsdNTc7bqzbzPIhVUse -C+6Kutx2mskiWtMvYh15kkfbuBnI3wtV98IFLhFaZGRy0NBH3sguQvEUb2tNzyBT -F5hUqzXsKlmH4wKYAZV1JNwaEn1UzLx52+1Qq4V1VKimEc/kfTJpwSbWF76LLB0s -GVEWd5wpG2KDLKeIxFptPj+HpEAfRTE/MSW/9zz1V7Be1gk4ZFrQikj2LbY+MIvy -Z76xYvZA5RN+dHD+FuAhPzuEP8YpsBKAHiMbbKvwE4cIHuacgK6uiGVxNYEha/dJ -Met7RrF5kPSs4KrAmdFEq07ebfLoBD4DDbjJ3TDiFrap8kXLsekNNuz2By04TRZv -KCZGqR1c9EVGuAU5GgXHYsBcGUHBy5SoEpeuRVrpLz8 --> ssh-ed25519 jIXfPA bUYlIimYWfzWGWlmXx2cvbhFIwqJV/d1VrFvdAIBKw8 -pyb/vVXXcY3NuVWwRF9QTLQUUvXbKXU4TY6FU7nBanE --> ssh-ed25519 um7xWA DX1EN7dexMvKKuf69E2rnWtQDRXBMf5d5/l1ej/DPhQ -8qeFNTNnBfArNGwEIqlbmuWQz9T4oJhjdzScoyPeyyc ---- GqybhtQAk+XPOg90zmdeUUOSmT/cHs5RLgxnif8gTNM -ؗ-<ݔX?('aUI_xCRpOjCgjrݵ ^_>6H~Vf)9@e=|}\Tş340'M﹏"ܻ(kVLFUSAtԓysSQ,Or]I,}n!ve}ct&Հ)r!;Unquwᢵ.v^Xͪ9w qg.6!1A>0o2T5< >p~g9#uZ%@\lZhb -sn$hR%FmPńVIF%ywl0G)ç3M -0:*q1Ԙq'YG_i(k q[&,]ovz1vv \ No newline at end of file +Fb0wbIz3bKDqqlzXonleDkfK9CWiXwW5o9LkvVuCIYwq/o0F69Sn0itWeIo1cQ+s +fL5QEsRRbTsYZ63O8uJvdr2867wv0pzGffmErXv+rzVZoFMqq2swJ5Mn0yUXzItH +CPhRoLHfewMZVgE+fti4UigZxiOtoPR1+Nsmn2NcypNaz5Y+3J75zSHDCrcbJr0L +/U5ACXMPpFocZcL9CO6lySxJNyaOFyl8sX1azCjSeN3C87OMsRBQ7YO0/UL6FxWp +cwGTcd+sEgrf1bybR9p7khdsbPybpLzFtjcCpXjncdhagPG1241ohWGbCAx9bHtl +mSWhz89RCn6Tf00drosGdLyhKR7JfLr76mFdmU3xmKkZZu6xMkeoXDmUdtOd52ri +iF/OcHXFD/G4Vx2Fd8z1sTgIOYP0cDpzcn4JkWK9WjVyg9o4FNz98sfTSKEEcZc3 +dQSNpokXW9NH4VFrOkkdUzFn7kE7ZL5QcHmrxKQKS4gJ/Keq/g+WirV9bZ6w0QbQ +75JLS8/LAI8uXHzDYDVggp851eZIOTFL1SchjSjN/6BN+e/H02r9HbxxGnmOjNuw +0nC9R6t2nnwElw64TZyZru9VHPGGqWFmuyNOqj53Cn5PGNpy5hEiycMhT+Leh0De +hv/cQVlfFbZfRK8PMWrsZi0P88AG5XgzeEtnuRPqjqE +-> ssh-ed25519 jIXfPA j9rQS9GZYQTZli6ORRchsXBkoknUp7vSKLIAnOP7iD0 +nwrzn2WAkFVYNn9l7BgDdwgB52Fg0yF3zVKZfdgJXAw +-> ssh-ed25519 um7xWA /uvTBZxHjZwq8y/hpfKtZE91IXlPpw9lrwP05lzsC3o +kSqDREa/Sd4PHHsEnr49Vlis1bP/baFiRR8Adx4QvZw +--- 3t77fHsF25pYb0OuVHB4FlJQp8nPq2rD7wbNK4dzqcE +(RQ@6_:<]|oځhiy<{,a B~o͞rI;K13R2ĩ7Yk>/P9_ݺ 6c.ۮ>b;n1~7i%ۉwB6{|>t{cDeGqO``ؿ{&V]Y[VC/JM 7s7otbPץ