Unverified Commit ef9ef9ea authored by Sandro Jäckel's avatar Sandro Jäckel Committed by GitHub
Browse files

mediagoblin: init at 0.14.0, nixos/mediagoblin: init (#350578)

parents be1866a8 ff9dd57d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1479,6 +1479,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
+377 −0
Original line number Diff line number Diff line
{
  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" ];
      };
    };
  };
}
+13 −0
Original line number Diff line number Diff line
{ 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")
  ];
}
+156 −0
Original line number Diff line number Diff line
{
  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;
  };
}
+45 −0
Original line number Diff line number Diff line
{
  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 ];
  };
}
Loading