From 7d797d8b678b4437132051492f42dda9f85b9ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 4 Oct 2024 05:49:36 +0200 Subject: [PATCH 1/3] python312Packages.wtforms-sqlalchemy: init at 0.4.1 --- .../wtforms-sqlalchemy/default.nix | 45 +++++++++++++++++++ pkgs/top-level/python-packages.nix | 2 + 2 files changed, 47 insertions(+) create mode 100644 pkgs/development/python-modules/wtforms-sqlalchemy/default.nix diff --git a/pkgs/development/python-modules/wtforms-sqlalchemy/default.nix b/pkgs/development/python-modules/wtforms-sqlalchemy/default.nix new file mode 100644 index 000000000000..4d48ed10846f --- /dev/null +++ b/pkgs/development/python-modules/wtforms-sqlalchemy/default.nix @@ -0,0 +1,45 @@ +{ + lib, + buildPythonPackage, + fetchFromGitHub, + pytestCheckHook, + setuptools, + sqlalchemy, + wtforms, +}: + +buildPythonPackage rec { + pname = "wtforms-sqlalchemy"; + version = "0.4.1"; + pyproject = true; + + src = fetchFromGitHub { + owner = "wtforms"; + repo = "wtforms-sqlalchemy"; + rev = version; + hash = "sha256-uR09LYfcyre+AC2TTEIhpjSI7y4Yo0GJ20smkzo5PRY="; + }; + + build-system = [ + setuptools + ]; + + dependencies = [ + sqlalchemy + wtforms + ]; + + nativeCheckInputs = [ + pytestCheckHook + ]; + + 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; + maintainers = with lib.maintainers; [ SuperSandro2000 ]; + }; +} diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index e26247c259f2..d15a84419ec7 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -17533,6 +17533,8 @@ self: super: with self; { wtforms-bootstrap5 = callPackage ../development/python-modules/wtforms-bootstrap5 { }; + wtforms-sqlalchemy = callPackage ../development/python-modules/wtforms-sqlalchemy { }; + wunsen = callPackage ../development/python-modules/wunsen { }; wtf-peewee = callPackage ../development/python-modules/wtf-peewee { }; From 3442e32b1be95228e4606125062446e530c76b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Wed, 22 Jun 2022 21:01:40 +0200 Subject: [PATCH 2/3] mediagoblin: init at 0.14.0 --- .../by-name/me/mediagoblin/bower-packages.nix | 13 ++ pkgs/by-name/me/mediagoblin/package.nix | 156 ++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 pkgs/by-name/me/mediagoblin/bower-packages.nix create mode 100644 pkgs/by-name/me/mediagoblin/package.nix diff --git a/pkgs/by-name/me/mediagoblin/bower-packages.nix b/pkgs/by-name/me/mediagoblin/bower-packages.nix new file mode 100644 index 000000000000..279a437a10ab --- /dev/null +++ b/pkgs/by-name/me/mediagoblin/bower-packages.nix @@ -0,0 +1,13 @@ +{ fetchbower, buildEnv }: +buildEnv { + name = "bower-env"; + ignoreCollisions = true; + paths = [ + (fetchbower "jquery" "2.1.4" "~2.1.3" "1ywrpk2xsr6ghkm3j9gfnl9r3jn6xarfamp99b0bcm57kq9fm2k0") + (fetchbower "video.js" "5.20.5" "~5.20.1" "1agvvid2valba7xxypknbb3k578jz8sa4rsmq5z2yc5010k3nkqp") + (fetchbower "videojs-resolution-switcher" "0.4.2" "~0.4.2" + "1bz2q1wwdglaxbb20fin9djgs1c71jywxhlrm16hm4bzg708ycaf" + ) + (fetchbower "leaflet" "0.7.7" "~0.7.3" "0jim285bljmxxngpm3yx6bnnd10n2whwkgmmhzpcd1rdksnr5nca") + ]; +} diff --git a/pkgs/by-name/me/mediagoblin/package.nix b/pkgs/by-name/me/mediagoblin/package.nix new file mode 100644 index 000000000000..bf57c6916282 --- /dev/null +++ b/pkgs/by-name/me/mediagoblin/package.nix @@ -0,0 +1,156 @@ +{ + lib, + buildBowerComponents, + fetchFromSourcehut, + gobject-introspection, + gst_all_1, + poppler_utils, + python3, + xorg, +}: + +let + python = python3.override { + packageOverrides = final: prev: { + celery = prev.celery.overridePythonAttrs { + doCheck = false; + }; + sqlalchemy = prev.sqlalchemy_1_4; + }; + }; + + version = "0.14.0"; + src = fetchFromSourcehut { + owner = "~mediagoblin"; + repo = "mediagoblin"; + rev = "v${version}"; + fetchSubmodules = true; + hash = "sha256-Y1VnXLHEl6TR8nt+vKSfoCwleQ+oA2WPMN9q4fW9R3s="; + }; + + extlib = buildBowerComponents { + name = "mediagoblin-extlib"; + generated = ./bower-packages.nix; + inherit src; + }; +in +python.pkgs.buildPythonApplication rec { + pname = "mediagoblin"; + inherit version src; + + postPatch = '' + # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/.guix/modules/mediagoblin-package.scm#L60-62 + cp mediagoblin/_version.py.in mediagoblin/_version.py + substituteInPlace mediagoblin/_version.py \ + --replace-fail "@PACKAGE_VERSION@" "${version}" + + # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/.guix/modules/mediagoblin-package.scm#L66-67 + substituteInPlace mediagoblin/gmg_commands/__init__.py \ + --replace-fail "ArgumentParser(" "ArgumentParser(prog=\"gmg\"," + ''; + + nativeBuildInputs = [ + gobject-introspection + python3.pkgs.babel + xorg.lndir + ]; + + build-system = with python.pkgs; [ + setuptools + ]; + + dependencies = with python.pkgs; [ + alembic + babel + bcrypt + celery + certifi + configobj + email-validator + exifread + feedgenerator + itsdangerous + jinja2 + jsonschema + lxml-html-clean + markdown + oauthlib + pastescript + pillow + pyld + python-dateutil + requests + soundfile + sqlalchemy + unidecode + waitress + werkzeug + wtforms + wtforms-sqlalchemy + + # undocumented and fails to start without + gst-python + pygobject3 + ]; + + optional-dependencies = + with python.pkgs; + let + # not really documented in python build system + gst = with gst_all_1; [ + # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/.guix/modules/mediagoblin-package.scm#L122-127 + gst-libav + gst-plugins-bad + gst-plugins-base + gst-plugins-good + gst-plugins-ugly + gstreamer + ]; + in + { + ascii = [ chardet ]; + audio = [ numpy ] ++ gst; + ldap = [ python-ldap ]; + openid = [ python3-openid ]; + raw_image = [ py3exiv2 ]; + video = [ pygobject3 ] ++ gst; + }; + + postBuild = '' + ./devtools/compile_translations.sh + ''; + + postInstall = '' + lndir -silent ${extlib}/bower_components/ $out/${python.sitePackages}/mediagoblin/static/extlib/ + + ln -rs $out/${python.sitePackages}/mediagoblin/static/extlib/jquery/dist/jquery.js $out/${python.sitePackages}/mediagoblin/static/js/extlib/jquery.js + ln -rs $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/dist/leaflet.css $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/leaflet.css + ln -rs $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/dist/leaflet.js $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/leaflet.js + ln -rs $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/dist/images/ $out/${python.sitePackages}/mediagoblin/static/extlib/leaflet/ + ''; + + nativeCheckInputs = + with python.pkgs; + [ + pytest-forked + pytest-xdist + pytestCheckHook + webtest + + poppler_utils + ] + ++ lib.flatten (lib.attrValues optional-dependencies); + + pythonImportsCheck = [ "mediagoblin" ]; + + passthru = { + inherit python; + }; + + meta = { + description = "Free software media publishing platform that anyone can run"; + homepage = "https://mediagoblin.org/"; + license = lib.licenses.agpl3Plus; + maintainers = lib.teams.c3d2.members; + }; +} From ff9dd57d3dda785fbe842d3d324df2a44a54d9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Sun, 6 Oct 2024 03:27:47 +0200 Subject: [PATCH 3/3] nixos/mediagoblin: init --- nixos/modules/module-list.nix | 1 + .../modules/services/web-apps/mediagoblin.nix | 377 ++++++++++++++++++ 2 files changed, 378 insertions(+) create mode 100644 nixos/modules/services/web-apps/mediagoblin.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index dabeebb34e1e..b1a5ced80d50 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1474,6 +1474,7 @@ ./services/web-apps/ocis.nix ./services/web-apps/onlyoffice.nix ./services/web-apps/openvscode-server.nix + ./services/web-apps/mediagoblin.nix ./services/web-apps/mobilizon.nix ./services/web-apps/openwebrx.nix ./services/web-apps/outline.nix diff --git a/nixos/modules/services/web-apps/mediagoblin.nix b/nixos/modules/services/web-apps/mediagoblin.nix new file mode 100644 index 000000000000..a6f2e5cdd0a3 --- /dev/null +++ b/nixos/modules/services/web-apps/mediagoblin.nix @@ -0,0 +1,377 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.mediagoblin; + + mkSubSectionKeyValue = + depth: k: v: + if lib.isAttrs v then + let + inherit (lib.strings) replicate; + in + "${replicate depth "["}${k}${replicate depth "]"}\n" + + lib.generators.toINIWithGlobalSection { + mkKeyValue = mkSubSectionKeyValue (depth + 1); + } { globalSection = v; } + else + lib.generators.mkKeyValueDefault { + mkValueString = v: if lib.isString v then ''"${v}"'' else lib.generators.mkValueStringDefault { } v; + } " = " k v; + + iniFormat = pkgs.formats.ini { }; + + # we need to build our own GI_TYPELIB_PATH because celery and paster need this information, too and cannot easily be re-wrapped + GI_TYPELIB_PATH = + let + needsGst = + (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.audio") + || (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.video"); + in + lib.makeSearchPathOutput "out" "lib/girepository-1.0" ( + with pkgs.gst_all_1; + [ + pkgs.glib + gst-plugins-base + gstreamer + ] + # audio and video share most dependencies, so we can just take audio + ++ lib.optionals needsGst cfg.package.optional-dependencies.audio + ); + + finalPackage = cfg.package.python.buildEnv.override { + extraLibs = + with cfg.package.python.pkgs; + [ + (toPythonModule cfg.package) + ] + ++ cfg.pluginPackages + # not documented in extras... + ++ lib.optional (lib.hasPrefix "postgresql://" cfg.settings.mediagoblin.sql_engine) psycopg2 + ++ ( + let + inherit (cfg.settings.mediagoblin) plugins; + in + with cfg.package.optional-dependencies; + lib.optionals (plugins ? "mediagoblin.media_types.audio") audio + ++ lib.optionals (plugins ? "mediagoblin.media_types.video") video + ++ lib.optionals (plugins ? "mediagoblin.media_types.raw_image") raw_image + ++ lib.optionals (plugins ? "mediagoblin.media_types.ascii") ascii + ++ lib.optionals (plugins ? "mediagoblin.plugins.ldap") ldap + ); + }; +in +{ + options = { + services.mediagoblin = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable MediaGoblin. + + After the initial deployment, make sure to add an admin account: + ``` + mediagoblin-gmg adduser --username admin --email admin@example.com + mediagoblin-gmg makeadmin admin + ``` + ''; + }; + + domain = lib.mkOption { + type = lib.types.str; + example = "mediagoblin.example.com"; + description = "Domain under which mediagoblin will be served."; + }; + + createDatabaseLocally = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = "Whether to configure a local postgres database and connect to it."; + }; + + package = lib.mkPackageOption pkgs "mediagoblin" { }; + + pluginPackages = lib.mkOption { + type = with lib.types; listOf package; + default = [ ]; + description = "Plugins to add to the environment of MediaGoblin. They still need to be enabled in the config."; + }; + + settings = lib.mkOption { + description = "Settings which are written into `mediagoblin.ini`."; + default = { }; + type = lib.types.submodule { + freeformType = lib.types.anything; + + options = { + mediagoblin = { + allow_registration = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to enable user self registration. This is generally not recommend due to spammers. + See [upstream FAQ](https://docs.mediagoblin.org/en/stable/siteadmin/production-deployments.html#should-i-keep-open-registration-enabled). + ''; + }; + + email_debug_mode = lib.mkOption { + type = lib.types.bool; + default = true; + example = false; + description = '' + Disable email debug mode to start sending outgoing mails. + This requires configuring SMTP settings, + see the [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/configuration.html#enabling-email-notifications) + for details. + ''; + }; + + email_sender_address = lib.mkOption { + type = lib.types.str; + example = "noreply@example.org"; + description = "Email address which notices are sent from."; + }; + + sql_engine = lib.mkOption { + type = lib.types.str; + default = "sqlite:///var/lib/mediagoblin/mediagoblin.db"; + example = "postgresql:///mediagoblin"; + description = "Database to use."; + }; + + plugins = lib.mkOption { + defaultText = '' + { + "mediagoblin.plugins.geolocation" = { }; + "mediagoblin.plugins.processing_info" = { }; + "mediagoblin.plugins.basic_auth" = { }; + "mediagoblin.media_types.image" = { }; + } + ''; + description = '' + Plugins to enable. See [upstream docs](https://docs.mediagoblin.org/en/stable/siteadmin/plugins.html) for details. + Extra dependencies are automatically enabled. + ''; + }; + }; + }; + }; + }; + + paste = { + port = lib.mkOption { + type = lib.types.port; + default = 6543; + description = "Port under which paste will listen."; + }; + + settings = lib.mkOption { + description = "Settings which are written into `paste.ini`."; + default = { }; + type = lib.types.submodule { + freeformType = iniFormat.type; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + (pkgs.writeShellScriptBin "mediagoblin-gmg" '' + sudo=exec + if [[ "$USER" != mediagoblin ]]; then + sudo='exec /run/wrappers/bin/sudo -u mediagoblin' + fi + $sudo sh -c "cd /var/lib/mediagoblin; env GI_TYPELIB_PATH=${GI_TYPELIB_PATH} ${lib.getExe' finalPackage "gmg"} $@" + '') + ]; + + services = { + mediagoblin.settings.mediagoblin = { + plugins = { + "mediagoblin.media_types.image" = { }; + "mediagoblin.plugins.basic_auth" = { }; + "mediagoblin.plugins.geolocation" = { }; + "mediagoblin.plugins.processing_info" = { }; + }; + sql_engine = lib.mkIf cfg.createDatabaseLocally "postgresql:///mediagoblin"; + }; + + nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + virtualHosts = { + # see https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/nginx.conf.template + "${cfg.domain}" = { + forceSSL = true; + extraConfig = '' + # https://git.sr.ht/~mediagoblin/mediagoblin/tree/bf61d38df21748aadb480c53fdd928647285e35f/item/Dockerfile.nginx.in#L5 + client_max_body_size 100M; + + more_set_headers X-Content-Type-Options nosniff; + ''; + locations = { + "/".proxyPass = "http://127.0.0.1:${toString cfg.paste.port}"; + "/mgoblin_static/".alias = "${finalPackage}/${finalPackage.python.sitePackages}/mediagoblin/static/"; + "/mgoblin_media/".alias = "/var/lib/mediagoblin/user_dev/media/public/"; + "/theme_static/".alias = "/var/lib/mediagoblin/user_dev/theme_static/"; + "/plugin_static/".alias = "/var/lib/mediagoblin/user_dev/plugin_static/"; + }; + }; + }; + }; + + postgresql = lib.mkIf cfg.createDatabaseLocally { + enable = true; + ensureDatabases = [ "mediagoblin" ]; + ensureUsers = [ + { + name = "mediagoblin"; + ensureDBOwnership = true; + } + ]; + }; + + rabbitmq.enable = true; + }; + + systemd.services = + let + serviceDefaults = { + wantedBy = [ "multi-user.target" ]; + path = + lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.stl") [ pkgs.blender ] + ++ lib.optionals (cfg.settings.mediagoblin.plugins ? "mediagoblin.media_types.pdf") ( + with pkgs; + [ + poppler_utils + unoconv + ] + ); + serviceConfig = { + AmbientCapabilities = ""; + CapabilityBoundingSet = [ "" ]; + DevicePolicy = "closed"; + Group = "mediagoblin"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProcSubset = "pid"; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RemoveIPC = true; + StateDirectory = "mediagoblin"; + StateDirectoryMode = "0750"; + User = "mediagoblin"; + WorkingDirectory = "/var/lib/mediagoblin/"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "@chown" + ]; + UMask = "0027"; + }; + }; + + generatedPasteConfig = iniFormat.generate "paste.ini" cfg.paste.settings; + pasteConfig = pkgs.runCommand "paste-combined.ini" { nativeBuildInputs = [ pkgs.crudini ]; } '' + cp ${cfg.package.src}/paste.ini $out + chmod +w $out + crudini --merge $out < ${generatedPasteConfig} + ''; + in + { + mediagoblin-celeryd = lib.recursiveUpdate serviceDefaults { + # we cannot change DEFAULT.data_dir inside mediagoblin.ini because of an annoying bug + # https://todo.sr.ht/~mediagoblin/mediagoblin/57 + preStart = '' + cp --remove-destination ${ + pkgs.writeText "mediagoblin.ini" ( + lib.generators.toINI { } (lib.filterAttrsRecursive (n: v: n != "plugins") cfg.settings) + + "\n" + + lib.generators.toINI { mkKeyValue = mkSubSectionKeyValue 2; } { + inherit (cfg.settings.mediagoblin) plugins; + } + ) + } /var/lib/mediagoblin/mediagoblin.ini + ''; + serviceConfig = { + Environment = [ + "CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_celery" + "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" + "MEDIAGOBLIN_CONFIG=/var/lib/mediagoblin/mediagoblin.ini" + "PASTE_CONFIG=${pasteConfig}" + ]; + ExecStart = "${lib.getExe' finalPackage "celery"} worker --loglevel=INFO"; + }; + unitConfig.Description = "MediaGoblin Celery"; + }; + + mediagoblin-paster = lib.recursiveUpdate serviceDefaults { + after = [ + "mediagoblin-celeryd.service" + "postgresql.service" + ]; + requires = [ + "mediagoblin-celeryd.service" + "postgresql.service" + ]; + preStart = '' + cp --remove-destination ${pasteConfig} /var/lib/mediagoblin/paste.ini + ${lib.getExe' finalPackage "gmg"} dbupdate + ''; + serviceConfig = { + Environment = [ + "CELERY_ALWAYS_EAGER=false" + "GI_TYPELIB_PATH=${GI_TYPELIB_PATH}" + ]; + ExecStart = "${lib.getExe' finalPackage "paster"} serve /var/lib/mediagoblin/paste.ini"; + }; + unitConfig.Description = "Mediagoblin"; + }; + }; + + systemd.tmpfiles.settings."mediagoblin"."/var/lib/mediagoblin/user_dev".d = { + group = "mediagoblin"; + mode = "2750"; + user = "mediagoblin"; + }; + + users = { + groups.mediagoblin = { }; + users = { + mediagoblin = { + group = "mediagoblin"; + home = "/var/lib/mediagoblin"; + isSystemUser = true; + }; + nginx.extraGroups = [ "mediagoblin" ]; + }; + }; + }; +}