about summary refs log tree commit diff
diff options
context:
space:
mode:
authorKerstin Humm <kerstin@erictapen.name>2023-09-05 17:53:09 +0200
committerYt <happysalada@tuta.io>2023-09-07 08:59:40 +0000
commit36ff7d5d5d5132d9177e36ae817a485cb5eb40db (patch)
treeba6609c6ab868f584beeb7d8e93d1b1b53965aed
parente5b6a21a70e6cd9100b656373fa1dbd85460640c (diff)
mobilizon: init at 3.1.3
Co-Authored-By: Minijackson <minijackson@riseup.net>
Co-Authored-By: summersamara <summersamara@proton.me>
-rw-r--r--nixos/modules/module-list.nix1
-rw-r--r--nixos/modules/services/web-apps/mobilizon.nix442
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/mobilizon.nix44
-rw-r--r--pkgs/servers/mobilizon/common.nix14
-rw-r--r--pkgs/servers/mobilizon/default.nix165
-rw-r--r--pkgs/servers/mobilizon/frontend.nix42
-rw-r--r--pkgs/servers/mobilizon/mix.nix2001
-rw-r--r--pkgs/servers/mobilizon/package.json140
-rw-r--r--pkgs/top-level/all-packages.nix4
10 files changed, 2854 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d650e5ec76b4c..b7582d4a6324d 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1260,6 +1260,7 @@
   ./services/web-apps/node-red.nix
   ./services/web-apps/onlyoffice.nix
   ./services/web-apps/openvscode-server.nix
+  ./services/web-apps/mobilizon.nix
   ./services/web-apps/openwebrx.nix
   ./services/web-apps/outline.nix
   ./services/web-apps/peering-manager.nix
diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix
new file mode 100644
index 0000000000000..4e796e2bc80cf
--- /dev/null
+++ b/nixos/modules/services/web-apps/mobilizon.nix
@@ -0,0 +1,442 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mobilizon;
+
+  user = "mobilizon";
+  group = "mobilizon";
+
+  settingsFormat = pkgs.formats.elixirConf { elixir = pkgs.elixir_1_14; };
+
+  configFile = settingsFormat.generate "mobilizon-config.exs" cfg.settings;
+
+  # Make a package containing launchers with the correct envirenment, instead of
+  # setting it with systemd services, so that the user can also use them without
+  # troubles
+  launchers = pkgs.stdenv.mkDerivation rec {
+    pname = "${cfg.package.pname}-launchers";
+    inherit (cfg.package) version;
+
+    src = cfg.package;
+
+    nativeBuildInputs = with pkgs; [ makeWrapper ];
+
+    dontBuild = true;
+
+    installPhase = ''
+      mkdir -p $out/bin
+
+      makeWrapper \
+        $src/bin/mobilizon \
+        $out/bin/mobilizon \
+        --run '. ${secretEnvFile}' \
+        --set MOBILIZON_CONFIG_PATH "${configFile}" \
+        --set-default RELEASE_TMP "/tmp"
+
+      makeWrapper \
+        $src/bin/mobilizon_ctl \
+        $out/bin/mobilizon_ctl \
+        --run '. ${secretEnvFile}' \
+        --set MOBILIZON_CONFIG_PATH "${configFile}" \
+        --set-default RELEASE_TMP "/tmp"
+    '';
+  };
+
+  repoSettings = cfg.settings.":mobilizon"."Mobilizon.Storage.Repo";
+  instanceSettings = cfg.settings.":mobilizon".":instance";
+
+  isLocalPostgres = repoSettings.socket_dir != null;
+
+  dbUser = if repoSettings.username != null then repoSettings.username else "mobilizon";
+
+  postgresql = config.services.postgresql.package;
+  postgresqlSocketDir = "/var/run/postgresql";
+
+  secretEnvFile = "/var/lib/mobilizon/secret-env.sh";
+in
+{
+  options = {
+    services.mobilizon = {
+      enable = mkEnableOption
+        "Mobilizon federated organization and mobilization platform";
+
+      nginx.enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Whether an <literal>nginx</literal> virtual host should be
+          set up to serve Mobilizon.
+        '';
+      };
+
+      package = mkPackageOptionMD pkgs "mobilizon" { };
+
+      settings = mkOption {
+        type =
+          let
+            elixirTypes = settingsFormat.lib.types;
+          in
+          types.submodule {
+            freeformType = settingsFormat.type;
+
+            options = {
+              ":mobilizon" = {
+
+                "Mobilizon.Web.Endpoint" = {
+                  url.host = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = literalExpression ''
+                      ''${settings.":mobilizon".":instance".hostname}
+                    '';
+                    description = ''
+                      Your instance's hostname for generating URLs throughout the app
+                    '';
+                  };
+
+                  http = {
+                    port = mkOption {
+                      type = elixirTypes.port;
+                      default = 4000;
+                      description = ''
+                        The port to run the server
+                      '';
+                    };
+                    ip = mkOption {
+                      type = elixirTypes.tuple;
+                      default = settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ];
+                      description = ''
+                        The IP address to listen on. Defaults to [::1] notated as a byte tuple.
+                      '';
+                    };
+                  };
+
+                  has_reverse_proxy = mkOption {
+                    type = elixirTypes.bool;
+                    default = true;
+                    description = ''
+                      Whether you use a reverse proxy
+                    '';
+                  };
+                };
+
+                ":instance" = {
+                  name = mkOption {
+                    type = elixirTypes.str;
+                    description = ''
+                      The fallback instance name if not configured into the admin UI
+                    '';
+                  };
+
+                  hostname = mkOption {
+                    type = elixirTypes.str;
+                    description = ''
+                      Your instance's hostname
+                    '';
+                  };
+
+                  email_from = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = literalExpression ''
+                      noreply@''${settings.":mobilizon".":instance".hostname}
+                    '';
+                    description = ''
+                      The email for the From: header in emails
+                    '';
+                  };
+
+                  email_reply_to = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = literalExpression ''
+                      ''${email_from}
+                    '';
+                    description = ''
+                      The email for the Reply-To: header in emails
+                    '';
+                  };
+                };
+
+                "Mobilizon.Storage.Repo" = {
+                  socket_dir = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = postgresqlSocketDir;
+                    description = ''
+                      Path to the postgres socket directory.
+
+                      Set this to null if you want to connect to a remote database.
+
+                      If non-null, the local PostgreSQL server will be configured with
+                      the configured database, permissions, and required extensions.
+
+                      If connecting to a remote database, please follow the
+                      instructions on how to setup your database:
+                      <link xlink:href="https://docs.joinmobilizon.org/administration/install/release/#database-setup"/>
+                    '';
+                  };
+
+                  username = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = user;
+                    description = ''
+                      User used to connect to the database
+                    '';
+                  };
+
+                  database = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = "mobilizon_prod";
+                    description = ''
+                      Name of the database
+                    '';
+                  };
+                };
+              };
+            };
+          };
+        default = { };
+
+        description = ''
+          Mobilizon Elixir documentation, see
+          <link xlink:href="https://docs.joinmobilizon.org/administration/configure/reference/"/>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.nginx.enable -> (cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.ip == settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ]);
+        message = "Setting the IP mobilizon listens on is only possible when the nginx config is not used, as it is hardcoded there.";
+      }
+    ];
+
+    services.mobilizon.settings = {
+      ":mobilizon" = {
+        "Mobilizon.Web.Endpoint" = {
+          server = true;
+          url.host = mkDefault instanceSettings.hostname;
+          secret_key_base =
+            settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_INSTANCE_SECRET"; };
+        };
+
+        "Mobilizon.Web.Auth.Guardian".secret_key =
+          settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_AUTH_SECRET"; };
+
+        ":instance" = {
+          registrations_open = mkDefault false;
+          demo = mkDefault false;
+          email_from = mkDefault "noreply@${instanceSettings.hostname}";
+          email_reply_to = mkDefault instanceSettings.email_from;
+        };
+
+        "Mobilizon.Storage.Repo" = {
+          # Forced by upstream since it uses PostgreSQL-specific extensions
+          adapter = settingsFormat.lib.mkAtom "Ecto.Adapters.Postgres";
+          pool_size = mkDefault 10;
+        };
+      };
+
+      ":tzdata".":data_dir" = "/var/lib/mobilizon/tzdata/";
+    };
+
+    # This somewhat follows upstream's systemd service here:
+    # https://framagit.org/framasoft/mobilizon/-/blob/master/support/systemd/mobilizon.service
+    systemd.services.mobilizon = {
+      description = "Mobilizon federated organization and mobilization platform";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [
+        gawk
+        imagemagick
+        libwebp
+        file
+
+        # Optional:
+        gifsicle
+        jpegoptim
+        optipng
+        pngquant
+      ];
+
+      serviceConfig = {
+        ExecStartPre = "${launchers}/bin/mobilizon_ctl migrate";
+        ExecStart = "${launchers}/bin/mobilizon start";
+        ExecStop = "${launchers}/bin/mobilizon stop";
+
+        User = user;
+        Group = group;
+
+        StateDirectory = "mobilizon";
+
+        Restart = "on-failure";
+
+        PrivateTmp = true;
+        ProtectSystem = "full";
+        NoNewPrivileges = true;
+
+        ReadWritePaths = mkIf isLocalPostgres postgresqlSocketDir;
+      };
+    };
+
+    # Create the needed secrets before running Mobilizon, so that they are not
+    # in the nix store
+    #
+    # Since some of these tasks are quite common for Elixir projects (COOKIE for
+    # every BEAM project, Phoenix and Guardian are also quite common), this
+    # service could be abstracted in the future, and used by other Elixir
+    # projects.
+    systemd.services.mobilizon-setup-secrets = {
+      description = "Mobilizon setup secrets";
+      before = [ "mobilizon.service" ];
+      wantedBy = [ "mobilizon.service" ];
+
+      script =
+        let
+          # Taken from here:
+          # https://framagit.org/framasoft/mobilizon/-/blob/1.0.7/lib/mix/tasks/mobilizon/instance.ex#L132-133
+          genSecret =
+            "IO.puts(:crypto.strong_rand_bytes(64)" +
+            "|> Base.encode64()" +
+            "|> binary_part(0, 64))";
+
+          # Taken from here:
+          # https://github.com/elixir-lang/elixir/blob/v1.11.3/lib/mix/lib/mix/release.ex#L499
+          genCookie = "IO.puts(Base.encode32(:crypto.strong_rand_bytes(32)))";
+
+          evalElixir = str: ''
+            ${pkgs.elixir_1_14}/bin/elixir --eval '${str}'
+          '';
+        in
+        ''
+          set -euxo pipefail
+
+          if [ ! -f "${secretEnvFile}" ]; then
+            install -m 600 /dev/null "${secretEnvFile}"
+            cat > "${secretEnvFile}" <<EOF
+          # This file was automatically generated by mobilizon-setup-secrets.service
+          export MOBILIZON_AUTH_SECRET='$(${evalElixir genSecret})'
+          export MOBILIZON_INSTANCE_SECRET='$(${evalElixir genSecret})'
+          export RELEASE_COOKIE='$(${evalElixir genCookie})'
+          EOF
+          fi
+        '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        StateDirectory = "mobilizon";
+      };
+    };
+
+    # Add the required PostgreSQL extensions to the local PostgreSQL server,
+    # if local PostgreSQL is configured.
+    systemd.services.mobilizon-postgresql = mkIf isLocalPostgres {
+      description = "Mobilizon PostgreSQL setup";
+
+      after = [ "postgresql.service" ];
+      before = [ "mobilizon.service" "mobilizon-setup-secrets.service" ];
+      wantedBy = [ "mobilizon.service" ];
+
+      path = [ postgresql ];
+
+      # Taken from here:
+      # https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
+      script =
+        ''
+          psql "${repoSettings.database}" -c "\
+            CREATE EXTENSION IF NOT EXISTS postgis; \
+            CREATE EXTENSION IF NOT EXISTS pg_trgm; \
+            CREATE EXTENSION IF NOT EXISTS unaccent;"
+        '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = config.services.postgresql.superUser;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/mobilizon/uploads/exports/csv 700 mobilizon mobilizon - -"
+      "Z /var/lib/mobilizon 700 mobilizon mobilizon - -"
+    ];
+
+    services.postgresql = mkIf isLocalPostgres {
+      enable = true;
+      ensureDatabases = [ repoSettings.database ];
+      ensureUsers = [
+        {
+          name = dbUser;
+          ensurePermissions = {
+            "DATABASE \"${repoSettings.database}\"" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+      extraPlugins = with postgresql.pkgs; [ postgis ];
+    };
+
+    # Nginx config taken from support/nginx/mobilizon-release.conf
+    services.nginx =
+      let
+        inherit (cfg.settings.":mobilizon".":instance") hostname;
+        proxyPass = "http://[::1]:"
+          + toString cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.port;
+      in
+      lib.mkIf cfg.nginx.enable {
+        enable = true;
+        virtualHosts."${hostname}" = {
+          enableACME = lib.mkDefault true;
+          forceSSL = lib.mkDefault true;
+          extraConfig = ''
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "upgrade";
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+          '';
+          locations."/" = {
+            inherit proxyPass;
+          };
+          locations."~ ^/(js|css|img)" = {
+            root = "${cfg.package}/lib/mobilizon-${cfg.package.version}/priv/static";
+            extraConfig = ''
+              etag off;
+              access_log off;
+              add_header Cache-Control "public, max-age=31536000, immutable";
+            '';
+          };
+          locations."~ ^/(media|proxy)" = {
+            inherit proxyPass;
+            extraConfig = ''
+              etag off;
+              access_log off;
+              add_header Cache-Control "public, max-age=31536000, immutable";
+            '';
+          };
+        };
+      };
+
+    users.users.${user} = {
+      description = "Mobilizon daemon user";
+      group = group;
+      isSystemUser = true;
+    };
+
+    users.groups.${group} = { };
+
+    # So that we have the `mobilizon` and `mobilizon_ctl` commands.
+    # The `mobilizon remote` command is useful for dropping a shell into the
+    # running Mobilizon instance, and `mobilizon_ctl` is used for common
+    # management tasks (e.g. adding users).
+    environment.systemPackages = [ launchers ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e5affdab88900..0037fb1893664 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -485,6 +485,7 @@ in {
   miriway = handleTest ./miriway.nix {};
   misc = handleTest ./misc.nix {};
   mjolnir = handleTest ./matrix/mjolnir.nix {};
+  mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
   monica = handleTest ./web-apps/monica.nix {};
diff --git a/nixos/tests/mobilizon.nix b/nixos/tests/mobilizon.nix
new file mode 100644
index 0000000000000..2b070ca9d9609
--- /dev/null
+++ b/nixos/tests/mobilizon.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ lib, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    mobilizonDomain = certs.domain;
+    port = 41395;
+  in
+
+  {
+    name = "mobilizon";
+    meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
+
+    nodes.server =
+      { ... }:
+      {
+        services.mobilizon = {
+          enable = true;
+          settings = {
+            ":mobilizon" = {
+              ":instance" = {
+                name = "Test Mobilizon";
+                hostname = mobilizonDomain;
+              };
+              "Mobilizon.Web.Endpoint".http.port = port;
+            };
+          };
+        };
+
+        security.pki.certificateFiles = [ certs.ca.cert ];
+
+        services.nginx.virtualHosts."${mobilizonDomain}" = {
+          enableACME = lib.mkForce false;
+          sslCertificate = certs.${mobilizonDomain}.cert;
+          sslCertificateKey = certs.${mobilizonDomain}.key;
+        };
+
+        networking.hosts."::1" = [ mobilizonDomain ];
+      };
+
+    testScript = ''
+      server.wait_for_unit("mobilizon.service")
+      server.wait_for_open_port(${toString port})
+      server.succeed("curl --fail https://${mobilizonDomain}/")
+    '';
+  })
diff --git a/pkgs/servers/mobilizon/common.nix b/pkgs/servers/mobilizon/common.nix
new file mode 100644
index 0000000000000..427e009e7ccab
--- /dev/null
+++ b/pkgs/servers/mobilizon/common.nix
@@ -0,0 +1,14 @@
+{ fetchFromGitLab }: rec {
+
+  pname = "mobilizon";
+  version = "3.1.3";
+
+  src = fetchFromGitLab {
+    domain = "framagit.org";
+    owner = "framasoft";
+    repo = pname;
+    rev = version;
+    sha256 = "sha256-vYn8wE3cwOH3VssPDKKWAV9ZLKMSGg6XVWFZzJ9HSw0=";
+  };
+
+}
diff --git a/pkgs/servers/mobilizon/default.nix b/pkgs/servers/mobilizon/default.nix
new file mode 100644
index 0000000000000..83b498347d188
--- /dev/null
+++ b/pkgs/servers/mobilizon/default.nix
@@ -0,0 +1,165 @@
+{ lib
+, beam
+, callPackage
+, writeShellScriptBin
+, writeText
+, yarn2nix
+, mix2nix
+, fetchFromGitLab
+, fetchFromGitHub
+, fetchgit
+, fetchurl
+, git
+, cmake
+, nixosTests
+, mobilizon-frontend
+}:
+
+let
+  beamPackages = beam.packages.erlangR25.extend (self: super: {
+    elixir = super.elixir_1_14;
+  });
+  inherit (beamPackages) mixRelease buildMix buildRebar3 fetchHex;
+  common = callPackage ./common.nix { };
+in
+mixRelease rec {
+  inherit (common) pname version src;
+
+  # See https://github.com/whitfin/cachex/issues/205
+  # This circumvents a startup error for now
+  stripDebug = false;
+
+  nativeBuildInputs = [ git cmake ];
+
+  mixNixDeps = import ./mix.nix {
+    inherit beamPackages lib;
+    overrides = (final: prev:
+      (lib.mapAttrs
+        (_: value: value.override {
+          appConfigPath = src + "/config";
+        })
+        prev) // {
+        fast_html = prev.fast_html.override {
+          nativeBuildInputs = [ cmake ];
+        };
+        ex_cldr = prev.ex_cldr.overrideAttrs (old: {
+          preBuild = "touch config/prod.exs";
+          # We have to use the GitHub sources, as it otherwise tries to download
+          # the locales at build time.
+          src = fetchFromGitHub {
+            owner = "erictapen";
+            repo = "cldr";
+            # tip of 2.37.1/compile_env-fix
+            rev = "3a0dcf91132542a739f7b2450c6df12d40edeb0a";
+            sha256 = "sha256-QQRt1HOuajCANbKxikdgN3oK9BdZJjg1qg+WHm4DuqY=";
+          };
+          postInstall = ''
+            cp $src/priv/cldr/locales/* $out/lib/erlang/lib/ex_cldr-${old.version}/priv/cldr/locales/
+          '';
+        });
+        ex_cldr_currencies = prev.ex_cldr_currencies.override {
+          preBuild = "touch config/prod.exs";
+        };
+        ex_cldr_numbers = prev.ex_cldr_numbers.override {
+          preBuild = "touch config/prod.exs";
+        };
+        ex_cldr_dates_times = prev.ex_cldr_dates_times.override {
+          preBuild = "touch config/prod.exs";
+        };
+        # Upstream issue: https://github.com/bryanjos/geo_postgis/pull/87
+        geo_postgis = prev.geo_postgis.overrideAttrs (old: {
+          propagatedBuildInputs = old.propagatedBuildInputs ++ [ final.ecto ];
+        });
+
+        # The remainder are Git dependencies (and their deps) that are not supported by mix2nix currently.
+        web_push_encryption = buildMix rec {
+          name = "web_push_encryption";
+          version = "0.3.1";
+          src = fetchFromGitHub {
+            owner = "danhper";
+            repo = "elixir-web-push-encryption";
+            rev = "70f00d06cbd88c9ac382e0ad2539e54448e9d8da";
+            sha256 = "sha256-b4ZMrt/8n2sPUFtCDRTwXS1qWm5VlYdbx8qC0R0boOA=";
+          };
+          beamDeps = with final; [ httpoison jose ];
+        };
+        icalendar = buildMix rec {
+          name = "icalendar";
+          version = "unstable-2022-04-10";
+          src = fetchFromGitHub {
+            owner = "tcitworld";
+            repo = name;
+            rev = "1033d922c82a7223db0ec138e2316557b70ff49f";
+            sha256 = "sha256-N3bJZznNazLewHS4c2B7LP1lgxd1wev+EWVlQ7rOwfU=";
+          };
+          beamDeps = with final; [ mix_test_watch ex_doc timex ];
+        };
+        erlport = buildRebar3 rec {
+          name = "erlport";
+          version = "0.10.1-compat";
+          src = fetchFromGitHub {
+            owner = "tcitworld";
+            repo = name;
+            rev = "1f8f4b1a50ecdf7e959090fb566ac45c63c39b0b";
+            sha256 = "sha256-NkoGAW+1MTL0p7uUHl89GcQsbcfyAg/sMr417jUWMNM=";
+          };
+        };
+        exkismet = buildMix rec {
+          name = "exkismet";
+          version = "0.0.1";
+          src = fetchFromGitHub {
+            owner = "tcitworld";
+            repo = name;
+            rev = "8b5485fde00fafbde20f315bec387a77f7358334";
+            sha256 = "sha256-ttgCWoBKU7VTjZJBhZNtqVF4kN7psBr/qOeR65MbTqw=";
+          };
+          beamDeps = with final; [ httpoison ];
+        };
+        rajska = buildMix rec {
+          name = "rajska";
+          version = "0.0.1";
+          src = fetchFromGitHub {
+            owner = "tcitworld";
+            repo = name;
+            rev = "0c036448e261e8be6a512581c592fadf48982d84";
+            sha256 = "sha256-4pfply1vTAIT2Xvm3kONmrCK05xKfXFvcb8EKoSCXBE=";
+          };
+          beamDeps = with final; [ httpoison absinthe ];
+        };
+
+      });
+  };
+
+  preConfigure = ''
+    export LANG=C.UTF-8 # fix elixir locale warning
+  '';
+
+  # Install the compiled js part
+  preBuild =
+    ''
+      cp -a "${mobilizon-frontend}/libexec/mobilizon/deps/priv/static" ./priv
+      chmod 770 -R ./priv
+    '';
+
+  postBuild = ''
+    mix phx.digest --no-deps-check
+  '';
+
+  passthru = {
+    tests.smoke-test = nixosTests.mobilizon;
+    updateScript = writeShellScriptBin "update.sh" ''
+      set -eou pipefail
+
+      SRC=$(nix path-info .#mobilizon.src)
+      ${mix2nix}/bin/mix2nix $SRC/mix.lock > pkgs/servers/mobilizon/mix.nix
+      cat $SRC/js/package.json > pkgs/servers/mobilizon/package.json
+    '';
+  };
+
+  meta = with lib; {
+    description = "Mobilizon is an online tool to help manage your events, your profiles and your groups";
+    homepage = "https://joinmobilizon.org/";
+    license = licenses.agpl3Plus;
+    maintainers = with maintainers; [ minijackson erictapen ];
+  };
+}
diff --git a/pkgs/servers/mobilizon/frontend.nix b/pkgs/servers/mobilizon/frontend.nix
new file mode 100644
index 0000000000000..adfdbf3950561
--- /dev/null
+++ b/pkgs/servers/mobilizon/frontend.nix
@@ -0,0 +1,42 @@
+{ lib, callPackage, mkYarnPackage, fetchYarnDeps, imagemagick }:
+
+let
+  common = callPackage ./common.nix { };
+in
+mkYarnPackage rec {
+  src = "${common.src}/js";
+
+  offlineCache = fetchYarnDeps {
+    yarnLock = src + "/yarn.lock";
+    sha256 = "sha256-yvId4NG1RABQd27RbSYki6AOFWWr5C97QPWEcnK77OE=";
+  };
+
+  packageJSON = ./package.json;
+
+  # Somehow $out/deps/mobilizon/node_modules ends up only containing nothing
+  # more than a .bin directory otherwise.
+  yarnPostBuild = ''
+    rm -rf $out/deps/mobilizon/node_modules
+    ln -s $out/node_modules $out/deps/mobilizon/node_modules
+  '';
+
+  buildPhase = ''
+    runHook preBuild
+
+    yarn run build
+
+    runHook postBuild
+  '';
+
+  doCheck = true;
+  checkPhase = "yarn run test";
+
+  nativeBuildInputs = [ imagemagick ];
+
+  meta = with lib; {
+    description = "Frontend for the Mobilizon server";
+    homepage = "https://joinmobilizon.org/";
+    license = licenses.agpl3Plus;
+    maintainers = with maintainers; [ minijackson erictapen ];
+  };
+}
diff --git a/pkgs/servers/mobilizon/mix.nix b/pkgs/servers/mobilizon/mix.nix
new file mode 100644
index 0000000000000..02515e056ed0a
--- /dev/null
+++ b/pkgs/servers/mobilizon/mix.nix
@@ -0,0 +1,2001 @@
+{ lib, beamPackages, overrides ? (x: y: {}) }:
+
+let
+  buildRebar3 = lib.makeOverridable beamPackages.buildRebar3;
+  buildMix = lib.makeOverridable beamPackages.buildMix;
+  buildErlangMk = lib.makeOverridable beamPackages.buildErlangMk;
+
+  self = packages // (overrides self packages);
+
+  packages = with beamPackages; with self; {
+    absinthe = buildMix rec {
+      name = "absinthe";
+      version = "1.7.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0mralw4b8lwczv0m064h59y8fjqh9cihhmi5mpxkpyl173cxpi60";
+      };
+
+      beamDeps = [ dataloader decimal nimble_parsec telemetry ];
+    };
+
+    absinthe_phoenix = buildMix rec {
+      name = "absinthe_phoenix";
+      version = "2.0.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "139gnamfbba5hyk1fx1zf8vfr0j17fd9q0vxxp9cf39qbj91hsfk";
+      };
+
+      beamDeps = [ absinthe absinthe_plug decimal phoenix phoenix_html phoenix_pubsub ];
+    };
+
+    absinthe_plug = buildMix rec {
+      name = "absinthe_plug";
+      version = "1.5.8";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0nkfk5gqbg8yvlysgxfcak7y4lsy8q2jfyqyhql5hwvvciv43c5v";
+      };
+
+      beamDeps = [ absinthe plug ];
+    };
+
+    argon2_elixir = buildMix rec {
+      name = "argon2_elixir";
+      version = "3.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0wyxj4197jnz4z38611f00ym5n3w7hv06l4l3dfid4h2xvhfm3y0";
+      };
+
+      beamDeps = [ comeonin elixir_make ];
+    };
+
+    atomex = buildMix rec {
+      name = "atomex";
+      version = "0.5.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0gxb379zfx5qk0ljxvy225iplxfvxbgfx470h8wm1f6abwdqjj32";
+      };
+
+      beamDeps = [ xml_builder ];
+    };
+
+    bunt = buildMix rec {
+      name = "bunt";
+      version = "0.2.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "19bp6xh052ql3ha0v3r8999cvja5d2p6cph02mxphfaj4jsbyc53";
+      };
+
+      beamDeps = [];
+    };
+
+    cachex = buildMix rec {
+      name = "cachex";
+      version = "3.6.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1qp2r1f4hvpybhgi547p33ci7bh2w6xn6jl9il68xg4370vlxwpb";
+      };
+
+      beamDeps = [ eternal jumper sleeplocks unsafe ];
+    };
+
+    castore = buildMix rec {
+      name = "castore";
+      version = "1.0.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "02rrljx2f6zhmiwqwyk7al0gdf66qpx4jm59sqg1cnyiylgb02k8";
+      };
+
+      beamDeps = [];
+    };
+
+    certifi = buildRebar3 rec {
+      name = "certifi";
+      version = "2.9.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0ha6vmf5p3xlbf5w1msa89frhvfk535rnyfybz9wdmh6vdms8v96";
+      };
+
+      beamDeps = [];
+    };
+
+    cldr_utils = buildMix rec {
+      name = "cldr_utils";
+      version = "2.24.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "101p43y2x6z6rd4ga5hr372z5y34rn3mm3j6pk84kf5m642k080q";
+      };
+
+      beamDeps = [ castore certifi decimal ];
+    };
+
+    codepagex = buildMix rec {
+      name = "codepagex";
+      version = "0.1.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0ndxsmalk70wqig1zzgl95g6mp2fb992y1l4y3nq3qnxjw84c88m";
+      };
+
+      beamDeps = [];
+    };
+
+    combine = buildMix rec {
+      name = "combine";
+      version = "0.10.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "06s5y8b0snr1s5ax9v3s7rc6c8xf5vj6878d1mc7cc07j0bvq78v";
+      };
+
+      beamDeps = [];
+    };
+
+    comeonin = buildMix rec {
+      name = "comeonin";
+      version = "5.3.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1pw4rhhsh8mwj26dkbxz2niih9j8pc3qijlpcl8jh208rg1cjf1y";
+      };
+
+      beamDeps = [];
+    };
+
+    connection = buildMix rec {
+      name = "connection";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1746n8ba11amp1xhwzp38yfii2h051za8ndxlwdykyqqljq1wb3j";
+      };
+
+      beamDeps = [];
+    };
+
+    cors_plug = buildMix rec {
+      name = "cors_plug";
+      version = "3.0.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "03c3vwp4bdk3sixica4mmg0vinmx8qdz2bmbby1x6bi7ijg7ab9z";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    cowboy = buildErlangMk rec {
+      name = "cowboy";
+      version = "2.10.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0sqxqjdykxc2ai9cvkc0xjwkvr80z98wzlqlrd1z3iiw32vwrz9s";
+      };
+
+      beamDeps = [ cowlib ranch ];
+    };
+
+    cowboy_telemetry = buildRebar3 rec {
+      name = "cowboy_telemetry";
+      version = "0.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1pn90is3k9dq64wbijvzkqb6ldfqvwiqi7ymc8dx6ra5xv0vm63x";
+      };
+
+      beamDeps = [ cowboy telemetry ];
+    };
+
+    cowlib = buildRebar3 rec {
+      name = "cowlib";
+      version = "2.12.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1c4dgi8canscyjgddp22mjc69znvwy44wk3r7jrl2wvs6vv76fqn";
+      };
+
+      beamDeps = [];
+    };
+
+    credo = buildMix rec {
+      name = "credo";
+      version = "1.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1mv9lyw6hgjn6hlnzfbs0x2dchvwlmj8bg0a8l7iq38z7pvgqfb8";
+      };
+
+      beamDeps = [ bunt file_system jason ];
+    };
+
+    credo_code_climate = buildMix rec {
+      name = "credo_code_climate";
+      version = "0.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1ji1d2qidnlz80pc3ql2ghwvhf1851c4fq0xh8ly5x2nh3irylkm";
+      };
+
+      beamDeps = [ credo jason ];
+    };
+
+    dataloader = buildMix rec {
+      name = "dataloader";
+      version = "1.0.10";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0gwnrlrvylar2mdlgi9y9pylk8c3la3c2k71mjrg9pcsq3771kal";
+      };
+
+      beamDeps = [ ecto telemetry ];
+    };
+
+    db_connection = buildMix rec {
+      name = "db_connection";
+      version = "2.5.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "18jsnmabdjwj3i7ml43ljzrzzvfy1a3bnbaqywgsv7nndji5nbf9";
+      };
+
+      beamDeps = [ telemetry ];
+    };
+
+    decimal = buildMix rec {
+      name = "decimal";
+      version = "2.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1k7z418b6cj977wswpxsk5844xrxc1smaiqsmrqpf3pdjzsfbksk";
+      };
+
+      beamDeps = [];
+    };
+
+    dialyxir = buildMix rec {
+      name = "dialyxir";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0vv90jip2w362n3l7dkhqfdwlz97nwji535kn3fbk3dassya9ch0";
+      };
+
+      beamDeps = [ erlex ];
+    };
+
+    digital_token = buildMix rec {
+      name = "digital_token";
+      version = "0.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "09kfwvjmjzikhy7p7s5dgkl9x011xvsb052fqfrmpvhz3pvfsy51";
+      };
+
+      beamDeps = [ cldr_utils jason ];
+    };
+
+    doctor = buildMix rec {
+      name = "doctor";
+      version = "0.21.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1q748x232m665qik23mb1rywp9267i0gmvfy9jr4wy3rm8fq69x2";
+      };
+
+      beamDeps = [ decimal ];
+    };
+
+    earmark_parser = buildMix rec {
+      name = "earmark_parser";
+      version = "1.4.32";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0md7rhw1ix4fp31bql9scvl4jpijixczm2ky7mxffwq3srvxvc5q";
+      };
+
+      beamDeps = [];
+    };
+
+    eblurhash = buildRebar3 rec {
+      name = "eblurhash";
+      version = "1.2.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0k040pj8hlm8mwy0ra459hk35v9gfsvvgp596nl27q2dj00cl84c";
+      };
+
+      beamDeps = [];
+    };
+
+    ecto = buildMix rec {
+      name = "ecto";
+      version = "3.10.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0d82qqlvhpi1mkqifayzzd0r0068l5csz1ml6i5qlr6py1w5g2ba";
+      };
+
+      beamDeps = [ decimal jason telemetry ];
+    };
+
+    ecto_autoslug_field = buildMix rec {
+      name = "ecto_autoslug_field";
+      version = "3.0.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "03ycq3c6sm79sx5cxsbv3yc1zvx0ss2a8mig0qr33wc5rz3m5hlf";
+      };
+
+      beamDeps = [ ecto slugger ];
+    };
+
+    ecto_dev_logger = buildMix rec {
+      name = "ecto_dev_logger";
+      version = "0.9.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1mf2068xqzv7dns2xyzl57cgm6hhbbdggvlni08cgz749a5wk2rf";
+      };
+
+      beamDeps = [ ecto jason ];
+    };
+
+    ecto_enum = buildMix rec {
+      name = "ecto_enum";
+      version = "1.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1r2ffrr020fhfviqn21cv06sd3sp4bf1jra0xrgb3hl1f445rdcg";
+      };
+
+      beamDeps = [ ecto ecto_sql postgrex ];
+    };
+
+    ecto_shortuuid = buildMix rec {
+      name = "ecto_shortuuid";
+      version = "0.1.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "152299sdkpmg3wlcyf9lyfp2r515c5lvqnnmjkg6alhjsz7ch5fj";
+      };
+
+      beamDeps = [ ecto shortuuid ];
+    };
+
+    ecto_sql = buildMix rec {
+      name = "ecto_sql";
+      version = "3.10.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0sy5277akp828hvcg60yxhpfgj543y2z1bqy2z414pv9ppdmp8pn";
+      };
+
+      beamDeps = [ db_connection ecto postgrex telemetry ];
+    };
+
+    elixir_feed_parser = buildMix rec {
+      name = "elixir_feed_parser";
+      version = "2.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "13x2rgckh41zkgbwg63wb7lvyy5xmn7vqq3i7nvy6virggz64g1d";
+      };
+
+      beamDeps = [ timex ];
+    };
+
+    elixir_make = buildMix rec {
+      name = "elixir_make";
+      version = "0.7.7";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0v3y9i3bif14486dliwn9arwd0pcp4nv24gjwnxm5b8gjpzrzhav";
+      };
+
+      beamDeps = [ castore ];
+    };
+
+    erlex = buildMix rec {
+      name = "erlex";
+      version = "0.2.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0x8c1j62y748ldvlh46sxzv5514rpzm809vxn594vd7y25by5lif";
+      };
+
+      beamDeps = [];
+    };
+
+    eternal = buildMix rec {
+      name = "eternal";
+      version = "1.2.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "10p7m6kv2z2c16gw36wgiwnkykss4lfkmm71llxp09ipkhmy77rc";
+      };
+
+      beamDeps = [];
+    };
+
+    ex_cldr = buildMix rec {
+      name = "ex_cldr";
+      version = "2.37.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0y5z2dkldf0a5zkdq9z9sxqbbnzp66azb92f81p9dg2lhhlc6q2x";
+      };
+
+      beamDeps = [ cldr_utils decimal gettext jason nimble_parsec ];
+    };
+
+    ex_cldr_calendars = buildMix rec {
+      name = "ex_cldr_calendars";
+      version = "1.22.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "12wf4v9r27skhc7gzhh78f22mraisg8gw47bhrbwx780hvh6vj61";
+      };
+
+      beamDeps = [ ex_cldr_numbers ex_doc jason ];
+    };
+
+    ex_cldr_currencies = buildMix rec {
+      name = "ex_cldr_currencies";
+      version = "2.15.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1y3g1q0g7ygcajqzqh9kb1y3kchbarkrf89nssi7fs66jrik2885";
+      };
+
+      beamDeps = [ ex_cldr jason ];
+    };
+
+    ex_cldr_dates_times = buildMix rec {
+      name = "ex_cldr_dates_times";
+      version = "2.13.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1b212qxx41h86x9hxzjlr7mb4j2zz54bs6jw5sxii4dxi5hj3cpm";
+      };
+
+      beamDeps = [ ex_cldr ex_cldr_calendars ex_cldr_numbers jason ];
+    };
+
+    ex_cldr_languages = buildMix rec {
+      name = "ex_cldr_languages";
+      version = "0.3.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0yj4rj4r0wdzhrwkg1xdg1x28d1pp3kk8fr45n3v9d5pfbpizyr2";
+      };
+
+      beamDeps = [ ex_cldr jason ];
+    };
+
+    ex_cldr_numbers = buildMix rec {
+      name = "ex_cldr_numbers";
+      version = "2.31.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1kxb3w0yl9c0m6kb16k969r6g0x4xp536zwp6jffdw30pzs40k59";
+      };
+
+      beamDeps = [ decimal digital_token ex_cldr ex_cldr_currencies jason ];
+    };
+
+    ex_cldr_plugs = buildMix rec {
+      name = "ex_cldr_plugs";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1zwbhpj9cxr2bl16cn5539hslj8gqi0q0xmfky27qjm17ra9i6k9";
+      };
+
+      beamDeps = [ ex_cldr gettext jason plug ];
+    };
+
+    ex_doc = buildMix rec {
+      name = "ex_doc";
+      version = "0.29.4";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1xf49d0ni08a83iankdj8fb6jyxm67wjl0gdwihwnimf6ykrjric";
+      };
+
+      beamDeps = [ earmark_parser makeup_elixir makeup_erlang ];
+    };
+
+    ex_ical = buildMix rec {
+      name = "ex_ical";
+      version = "0.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1yxyflmkvmglks7whmbz944kyq6qljjpki666dk9w9g058xlfxnv";
+      };
+
+      beamDeps = [ timex ];
+    };
+
+    ex_machina = buildMix rec {
+      name = "ex_machina";
+      version = "2.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1y2v4j1zg1ji8q8di0fxpc3z3n2jmbnc85d6hx68j4fykfisg6j1";
+      };
+
+      beamDeps = [ ecto ecto_sql ];
+    };
+
+    ex_optimizer = buildMix rec {
+      name = "ex_optimizer";
+      version = "0.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "11i2npj15w3a91lxdpf9ll7lpdv99zf7y9bg5yz6d2ympicw1xg6";
+      };
+
+      beamDeps = [ file_info ];
+    };
+
+    ex_unit_notifier = buildMix rec {
+      name = "ex_unit_notifier";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0rf3b2cb4fqsg0hgas9axr8p5p471ainzc788ky65ng8c9hgvzsm";
+      };
+
+      beamDeps = [];
+    };
+
+    excoveralls = buildMix rec {
+      name = "excoveralls";
+      version = "0.16.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0f7i5gx1rpswbqmmqv133v3lpjwpkhjb2k56fmqcy210ir367rys";
+      };
+
+      beamDeps = [ hackney jason ];
+    };
+
+    exgravatar = buildMix rec {
+      name = "exgravatar";
+      version = "2.0.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1q80jcrnagivsjr9mz0z9h4bwlf0xyyx6ijl7szd74c9ppwqz8dc";
+      };
+
+      beamDeps = [];
+    };
+
+    expo = buildMix rec {
+      name = "expo";
+      version = "0.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0idzpg3bc9ady0lqrkxs6y0x9daffjsapppfm9cf0vf545h56b62";
+      };
+
+      beamDeps = [];
+    };
+
+    export = buildMix rec {
+      name = "export";
+      version = "0.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "07q2l7f9yr3bd3xqc5q1qa9qrhnjzc9xnjrg6lj1hgq5yi7l99rx";
+      };
+
+      beamDeps = [ erlport ];
+    };
+
+    fast_html = buildMix rec {
+      name = "fast_html";
+      version = "2.0.5";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "01k51qri44535b1hwixlxk7151vph6vapswlfq918g245544ypv0";
+      };
+
+      beamDeps = [ elixir_make nimble_pool ];
+    };
+
+    fast_sanitize = buildMix rec {
+      name = "fast_sanitize";
+      version = "0.2.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1qjnbs63q0d95dqhh2r9sz3zpg2y4hjy23kxsqanwf6h21njibg8";
+      };
+
+      beamDeps = [ fast_html plug ];
+    };
+
+    file_info = buildMix rec {
+      name = "file_info";
+      version = "0.0.4";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "19c14xv0xzbl3m6y5p7dlxn8sfqi9bff8pv722837ff8q80svrsh";
+      };
+
+      beamDeps = [ mimetype_parser ];
+    };
+
+    file_system = buildMix rec {
+      name = "file_system";
+      version = "0.2.10";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1p0myxmnjjds8bbg69dd6fvhk8q3n7lb78zd4qvmjajnzgdmw6a1";
+      };
+
+      beamDeps = [];
+    };
+
+    floki = buildMix rec {
+      name = "floki";
+      version = "0.34.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0h936kfai562dh4qpcpri7jxrdmqyxaymizk9d5r55svx8748xwm";
+      };
+
+      beamDeps = [];
+    };
+
+    gen_smtp = buildRebar3 rec {
+      name = "gen_smtp";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0yb7541zx0x76gzk0m1m8fkl6524jhl8rxc59l6g5a5wh1b3gq2y";
+      };
+
+      beamDeps = [ ranch ];
+    };
+
+    geo = buildMix rec {
+      name = "geo";
+      version = "3.5.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "175mqwzdn4jkwxkklc3k97pbnc4mxjfsw49cld92nh0lyb2zsp66";
+      };
+
+      beamDeps = [ jason ];
+    };
+
+    geo_postgis = buildMix rec {
+      name = "geo_postgis";
+      version = "3.4.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1m5abb98a0kl24ysmawznqz2m5vylm17q88rvd35b003gzwwkn28";
+      };
+
+      beamDeps = [ geo jason postgrex ];
+    };
+
+    geohax = buildMix rec {
+      name = "geohax";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0912z8fn86r7sxgbvhmzfqvbxc96v5awklhmqrkwnfi10pwz4gl9";
+      };
+
+      beamDeps = [];
+    };
+
+    geolix = buildMix rec {
+      name = "geolix";
+      version = "2.0.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1pq1qcjaqsnnvsbgzzbfdj290n9mkp808cj45kppvfyhircbyhl7";
+      };
+
+      beamDeps = [];
+    };
+
+    geolix_adapter_mmdb2 = buildMix rec {
+      name = "geolix_adapter_mmdb2";
+      version = "0.6.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "16r6q2qmgdpj5pvb36s3rgfd1qm6rnzp8szqzp7i18z8x8prdzq6";
+      };
+
+      beamDeps = [ geolix mmdb2_decoder ];
+    };
+
+    gettext = buildMix rec {
+      name = "gettext";
+      version = "0.20.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0ggb458h60ch3inndqp9xhbailhb0jkq3xnp85sa94sy8dvv20qw";
+      };
+
+      beamDeps = [];
+    };
+
+    guardian = buildMix rec {
+      name = "guardian";
+      version = "2.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1hvl8ajr49f8lv2b79dbi33bn016kl2dchmd2vczl28vrbwl3qmv";
+      };
+
+      beamDeps = [ jose plug ];
+    };
+
+    guardian_db = buildMix rec {
+      name = "guardian_db";
+      version = "2.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0w0d9bwsiz7hw9i7b3v8gpjsvblzhbkcnnpxlzrrbhwjmi1xbrzq";
+      };
+
+      beamDeps = [ ecto ecto_sql guardian postgrex ];
+    };
+
+    guardian_phoenix = buildMix rec {
+      name = "guardian_phoenix";
+      version = "2.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1350z7sk4m8n2wxllnjm07gzpm8ybm3811i23wijn68mcwj3kx11";
+      };
+
+      beamDeps = [ guardian phoenix ];
+    };
+
+    hackney = buildRebar3 rec {
+      name = "hackney";
+      version = "1.18.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "13hja14kig5jnzcizpdghj68i88f0yd9wjdfjic9nzi98kzxmv54";
+      };
+
+      beamDeps = [ certifi idna metrics mimerl parse_trans ssl_verify_fun unicode_util_compat ];
+    };
+
+    hammer = buildMix rec {
+      name = "hammer";
+      version = "6.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0mxsd2psl3qddgflq17r5hc432hd15ccvayyj8ihfv9aard42zml";
+      };
+
+      beamDeps = [ poolboy ];
+    };
+
+    haversine = buildMix rec {
+      name = "haversine";
+      version = "0.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0q3hc16zd5pvz9xvhk5692v1fkk3fg42cw536yaaa65wjpl4ip2l";
+      };
+
+      beamDeps = [];
+    };
+
+    html_entities = buildMix rec {
+      name = "html_entities";
+      version = "0.5.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1k7xyj0q38ms3n5hbn782pa6w1vgd6biwlxr4db6319l828a6fy5";
+      };
+
+      beamDeps = [];
+    };
+
+    http_signatures = buildMix rec {
+      name = "http_signatures";
+      version = "0.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "18s2b5383xl2qjijkxag4mvwk2p5kv2fw58c9ii7pk12fc08lfyc";
+      };
+
+      beamDeps = [];
+    };
+
+    httpoison = buildMix rec {
+      name = "httpoison";
+      version = "1.8.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "08crb48yz7r7w00pzw9gfk862g99z2ma2x6awab0rqvjd7951crb";
+      };
+
+      beamDeps = [ hackney ];
+    };
+
+    idna = buildRebar3 rec {
+      name = "idna";
+      version = "6.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1sjcjibl34sprpf1dgdmzfww24xlyy34lpj7mhcys4j4i6vnwdwj";
+      };
+
+      beamDeps = [ unicode_util_compat ];
+    };
+
+    inet_cidr = buildMix rec {
+      name = "inet_cidr";
+      version = "1.0.4";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1g61i08cizr99ivy050lv8fmvnwia9zmipfvlwff8jkhi40x78k4";
+      };
+
+      beamDeps = [];
+    };
+
+    ip_reserved = buildMix rec {
+      name = "ip_reserved";
+      version = "0.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1zm4820yvdashvz14p9jhv2bwc1hshvyym1zx84yzjhiwavd5z2m";
+      };
+
+      beamDeps = [ inet_cidr ];
+    };
+
+    jason = buildMix rec {
+      name = "jason";
+      version = "1.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0891p2yrg3ri04p302cxfww3fi16pvvw1kh4r91zg85jhl87k8vr";
+      };
+
+      beamDeps = [ decimal ];
+    };
+
+    jose = buildMix rec {
+      name = "jose";
+      version = "1.11.5";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "115k981kfg9jmafgs16rybc5qah6p0zgvni3bdyfl0pyp8av5lyw";
+      };
+
+      beamDeps = [];
+    };
+
+    jumper = buildMix rec {
+      name = "jumper";
+      version = "1.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0cvlbfkapkvbwaijmjq3cxg5m6yv4rh69wvss9kfj862i83mk31i";
+      };
+
+      beamDeps = [];
+    };
+
+    junit_formatter = buildMix rec {
+      name = "junit_formatter";
+      version = "3.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0ax79m9fn7asbfjk523vvawnvbn2nbhgvnm6j6xdh5ac9fzca7vn";
+      };
+
+      beamDeps = [];
+    };
+
+    linkify = buildMix rec {
+      name = "linkify";
+      version = "0.5.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0xw14ls480jzha9fx4lxd40dff4xx82w1h87dr82az6lfw9mmwry";
+      };
+
+      beamDeps = [];
+    };
+
+    makeup = buildMix rec {
+      name = "makeup";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "19jpprryixi452jwhws3bbks6ki3wni9kgzah3srg22a3x8fsi8a";
+      };
+
+      beamDeps = [ nimble_parsec ];
+    };
+
+    makeup_elixir = buildMix rec {
+      name = "makeup_elixir";
+      version = "0.16.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1ik7qw0d5xyc7dv3n33qxl49jfk92l565lbv1zc9n80vmm0s69z1";
+      };
+
+      beamDeps = [ makeup nimble_parsec ];
+    };
+
+    makeup_erlang = buildMix rec {
+      name = "makeup_erlang";
+      version = "0.1.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "02411riqa713wzw8in582yva6n6spi4w1ndnj8nhjvnfjg5a3xgk";
+      };
+
+      beamDeps = [ makeup ];
+    };
+
+    meck = buildRebar3 rec {
+      name = "meck";
+      version = "0.9.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "09jq0jrsd3dwzjlnwqjv6m9r2rijgiv57yja6jl41p2p2db4yd41";
+      };
+
+      beamDeps = [];
+    };
+
+    metrics = buildRebar3 rec {
+      name = "metrics";
+      version = "1.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "05lz15piphyhvvm3d1ldjyw0zsrvz50d2m5f2q3s8x2gvkfrmc39";
+      };
+
+      beamDeps = [];
+    };
+
+    mime = buildMix rec {
+      name = "mime";
+      version = "2.0.5";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0p50h0ki8ay5sraiqxiajgwy1829bvyagj65bj9wjny4cnin83fs";
+      };
+
+      beamDeps = [];
+    };
+
+    mimerl = buildRebar3 rec {
+      name = "mimerl";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "08wkw73dy449n68ssrkz57gikfzqk3vfnf264s31jn5aa1b5hy7j";
+      };
+
+      beamDeps = [];
+    };
+
+    mimetype_parser = buildMix rec {
+      name = "mimetype_parser";
+      version = "0.1.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0lm8yzcmg17nhvr4p4dbmamb280a9dzqx4rwv66ffz40cz2q13vx";
+      };
+
+      beamDeps = [];
+    };
+
+    mix_test_watch = buildMix rec {
+      name = "mix_test_watch";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1hrfh7hg3iwmvzwwyshdddgjxwc2ci898lyak7c0zdybfv2b3djj";
+      };
+
+      beamDeps = [ file_system ];
+    };
+
+    mmdb2_decoder = buildMix rec {
+      name = "mmdb2_decoder";
+      version = "3.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "17amvw553l6drkaszc5xpf8rcz4fwzpm8kwl5mw29j7si3rz0sii";
+      };
+
+      beamDeps = [];
+    };
+
+    mock = buildMix rec {
+      name = "mock";
+      version = "0.3.8";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "08i0zvk3wss217pjr4qczmdgxi607wcp2mfinydxf5vnr5j27a3z";
+      };
+
+      beamDeps = [ meck ];
+    };
+
+    mogrify = buildMix rec {
+      name = "mogrify";
+      version = "0.9.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1rii2yjswnbivmdfnxljvqw3vlpgkhiqikz8k8mmyi97vvhv3281";
+      };
+
+      beamDeps = [];
+    };
+
+    mox = buildMix rec {
+      name = "mox";
+      version = "1.0.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1wpyh6wp76lyx0q2cys23rpmci4gj1pqwnqvfk467xxanchlk1pr";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_csv = buildMix rec {
+      name = "nimble_csv";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0amij6y3pgkpazhjr3madrn9c9lv6malq11ln1w82562zhbq2qnh";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_parsec = buildMix rec {
+      name = "nimble_parsec";
+      version = "1.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0rxiw6jzz77v0j460wmzcprhdgn71g1hrz3mcc6djn7bnb0f70i6";
+      };
+
+      beamDeps = [];
+    };
+
+    nimble_pool = buildMix rec {
+      name = "nimble_pool";
+      version = "0.2.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0gv59waa505mz2gi956sj1aa6844c65w2dp2qh2jfgsx15am0w8w";
+      };
+
+      beamDeps = [];
+    };
+
+    oauth2 = buildMix rec {
+      name = "oauth2";
+      version = "2.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0h9bps7gq7bac5gc3q0cgpsj46qnchpqbv5hzsnd2z9hnf2pzh4a";
+      };
+
+      beamDeps = [ tesla ];
+    };
+
+    oauther = buildMix rec {
+      name = "oauther";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0kn0msy3z0rx7l4pp8zc5r7yhvmwa3sscr08gfi2rivmm278isvq";
+      };
+
+      beamDeps = [];
+    };
+
+    oban = buildMix rec {
+      name = "oban";
+      version = "2.15.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1qgk5b5wsdavy0mwi1zklcl1sajxz693n0ayvn7lm1nvk1j63gk0";
+      };
+
+      beamDeps = [ ecto_sql jason postgrex telemetry ];
+    };
+
+    paasaa = buildMix rec {
+      name = "paasaa";
+      version = "0.6.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0blzqack3i8w1f0hjqjgsybqjazcyf54dv3anbnk225c3g1dybbk";
+      };
+
+      beamDeps = [];
+    };
+
+    parse_trans = buildRebar3 rec {
+      name = "parse_trans";
+      version = "3.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "12w8ai6b5s6b4hnvkav7hwxd846zdd74r32f84nkcmjzi1vrbk87";
+      };
+
+      beamDeps = [];
+    };
+
+    phoenix = buildMix rec {
+      name = "phoenix";
+      version = "1.7.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "127v5lrb0zflgm5cqwxsfjv76mizdldkzs66rdhb0as0h1vvxd7n";
+      };
+
+      beamDeps = [ castore jason phoenix_pubsub phoenix_template phoenix_view plug plug_cowboy plug_crypto telemetry websock_adapter ];
+    };
+
+    phoenix_ecto = buildMix rec {
+      name = "phoenix_ecto";
+      version = "4.4.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0pcgrvj5lqjmsngrhl77kv0l8ik8gg7pw19v4xlhpm818vfjw93h";
+      };
+
+      beamDeps = [ ecto phoenix_html plug ];
+    };
+
+    phoenix_html = buildMix rec {
+      name = "phoenix_html";
+      version = "3.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1lyhagjpg4lran6431csgkvf28g50mdvh4mlsxgs21j9vmp91ldy";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    phoenix_live_reload = buildMix rec {
+      name = "phoenix_live_reload";
+      version = "1.4.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1aqm6sxy4ijd5gi8lmjmcaxal1smg2smibjlzrkq9w6xwwsbizwv";
+      };
+
+      beamDeps = [ file_system phoenix ];
+    };
+
+    phoenix_live_view = buildMix rec {
+      name = "phoenix_live_view";
+      version = "0.19.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "157m7n71wfgd5c4gj9n120gxz5m6qrx3y7iriy5jxwwpyaqprici";
+      };
+
+      beamDeps = [ jason phoenix phoenix_html phoenix_template phoenix_view telemetry ];
+    };
+
+    phoenix_pubsub = buildMix rec {
+      name = "phoenix_pubsub";
+      version = "2.1.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "00p5dvizhawhqbia2cakdn4whaxsm2adq3lzfn3b137xvk0np85v";
+      };
+
+      beamDeps = [];
+    };
+
+    phoenix_swoosh = buildMix rec {
+      name = "phoenix_swoosh";
+      version = "1.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1fhxh4sff7b3qz2lyryzgms9d6mrhxnmlh924awid6p8a5r133g8";
+      };
+
+      beamDeps = [ hackney phoenix phoenix_html phoenix_view swoosh ];
+    };
+
+    phoenix_template = buildMix rec {
+      name = "phoenix_template";
+      version = "1.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1vlkd4z2bxinczwcysydidpnh49rpxjihb5k3k4k8qr2yrwc0z8m";
+      };
+
+      beamDeps = [ phoenix_html ];
+    };
+
+    phoenix_view = buildMix rec {
+      name = "phoenix_view";
+      version = "2.0.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0vykabqxyk08gkfm45zy5dnlnzygwx6g9z4z2h7fxix51qiyfad9";
+      };
+
+      beamDeps = [ phoenix_html phoenix_template ];
+    };
+
+    plug = buildMix rec {
+      name = "plug";
+      version = "1.14.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "04wdyv6nma74bj1m49vkm2bc5mjf8zclfg957fng8g71hw0wabw4";
+      };
+
+      beamDeps = [ mime plug_crypto telemetry ];
+    };
+
+    plug_cowboy = buildMix rec {
+      name = "plug_cowboy";
+      version = "2.6.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "04v6xc4v741dr2y38j66fmcc4xc037dnaxzkj2vih6j53yif2dny";
+      };
+
+      beamDeps = [ cowboy cowboy_telemetry plug ];
+    };
+
+    plug_crypto = buildMix rec {
+      name = "plug_crypto";
+      version = "1.2.5";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0hnqgzc3zas7j7wycgnkkdhaji5farkqccy2n4p1gqj5ccfrlm16";
+      };
+
+      beamDeps = [];
+    };
+
+    poolboy = buildRebar3 rec {
+      name = "poolboy";
+      version = "1.5.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1qq116314418jp4skxg8c6jx29fwp688a738lgaz6h2lrq29gmys";
+      };
+
+      beamDeps = [];
+    };
+
+    postgrex = buildMix rec {
+      name = "postgrex";
+      version = "0.17.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0md5j9k1vkwwzql68in6hmj0vfcdbnav33shxszf4fz7i2s5gc0l";
+      };
+
+      beamDeps = [ db_connection decimal jason ];
+    };
+
+    progress_bar = buildMix rec {
+      name = "progress_bar";
+      version = "2.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "11wgy5qxr7yn3f299v2rlab6vjyqas13fag7jjha6jgil9cfn695";
+      };
+
+      beamDeps = [ decimal ];
+    };
+
+    ranch = buildRebar3 rec {
+      name = "ranch";
+      version = "1.8.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1rfz5ld54pkd2w25jadyznia2vb7aw9bclck21fizargd39wzys9";
+      };
+
+      beamDeps = [];
+    };
+
+    remote_ip = buildMix rec {
+      name = "remote_ip";
+      version = "1.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0x7d086iik0h5gcwn2bvx6cjlznqxr1bznj6qlpsgmmadbvgsvv1";
+      };
+
+      beamDeps = [ combine plug ];
+    };
+
+    replug = buildMix rec {
+      name = "replug";
+      version = "0.1.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0q7sikahnvmr8jw9ziklh2am73h9cilhq1j697z59s24x5bpl7zp";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    sentry = buildMix rec {
+      name = "sentry";
+      version = "8.0.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "10qqkqvykw8v3xdw9j19jgg3kwjfdrp9sz3wg0vk2bqnf822s6h5";
+      };
+
+      beamDeps = [ hackney jason plug plug_cowboy ];
+    };
+
+    shortuuid = buildMix rec {
+      name = "shortuuid";
+      version = "2.1.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0lyk7bv89v3wfymg9jhml12rygi15s3wwpi7nscm3sh0fprw9c6r";
+      };
+
+      beamDeps = [];
+    };
+
+    sitemapper = buildMix rec {
+      name = "sitemapper";
+      version = "0.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0gr23zpwih1z2hycpbxgjnlvpg721f5z8savmh87zzp9wn2adxv0";
+      };
+
+      beamDeps = [ xml_builder ];
+    };
+
+    sleeplocks = buildRebar3 rec {
+      name = "sleeplocks";
+      version = "1.1.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "19argym7xifhsbrp21glkgs0dz1xpd00yfhsbhqdd0dpqm4d1rcz";
+      };
+
+      beamDeps = [];
+    };
+
+    slugger = buildMix rec {
+      name = "slugger";
+      version = "0.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1fmgnl4ydq4ivbfk1a934vcn0d0wb24lhnvcmqg5sq0jwz8dxl10";
+      };
+
+      beamDeps = [];
+    };
+
+    sobelow = buildMix rec {
+      name = "sobelow";
+      version = "0.12.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0p8wy0njiqmam9nsfjg0mgkmiwd4im6bhqjn2i8vc7amrryn22rg";
+      };
+
+      beamDeps = [ jason ];
+    };
+
+    ssl_verify_fun = buildRebar3 rec {
+      name = "ssl_verify_fun";
+      version = "1.1.6";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1026l1z1jh25z8bfrhaw0ryk5gprhrpnirq877zqhg253x3x5c5x";
+      };
+
+      beamDeps = [];
+    };
+
+    struct_access = buildMix rec {
+      name = "struct_access";
+      version = "1.1.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0sjq5v0zdqx7iqs0hqa7lfqzxf4jzi8rb409aywq2q12q3f13i74";
+      };
+
+      beamDeps = [];
+    };
+
+    sweet_xml = buildMix rec {
+      name = "sweet_xml";
+      version = "0.7.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1fpmwhqgvakvdpbwmmyh31ays3hzhnm9766xqyzp9zmkl5kwh471";
+      };
+
+      beamDeps = [];
+    };
+
+    swoosh = buildMix rec {
+      name = "swoosh";
+      version = "1.11.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0gyj99pg798frxn6nzw34nd661vlwl0pdbfzxd8x5sj2bl5mqfhp";
+      };
+
+      beamDeps = [ cowboy gen_smtp hackney jason mime plug_cowboy telemetry ];
+    };
+
+    telemetry = buildRebar3 rec {
+      name = "telemetry";
+      version = "1.2.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1mgyx9zw92g6w8fp9pblm3b0bghwxwwcbslrixq23ipzisfwxnfs";
+      };
+
+      beamDeps = [];
+    };
+
+    tesla = buildMix rec {
+      name = "tesla";
+      version = "1.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "04y31nq54j1wnzpi37779bzzq0sjwsh53ikvnh4n40nvpwgg0r1f";
+      };
+
+      beamDeps = [ castore hackney jason mime telemetry ];
+    };
+
+    timex = buildMix rec {
+      name = "timex";
+      version = "3.7.11";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1anijimbrb3ngdy6fdspr8c9hz6dip7nakx0gayzkfmsxzvj944b";
+      };
+
+      beamDeps = [ combine gettext tzdata ];
+    };
+
+    tz_world = buildMix rec {
+      name = "tz_world";
+      version = "1.3.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "140kip660zbfxpckzi2y4mxg1pysvwcr2cc68viqrd4r12m6bdbq";
+      };
+
+      beamDeps = [ castore certifi geo jason ];
+    };
+
+    tzdata = buildMix rec {
+      name = "tzdata";
+      version = "1.1.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "11wpm1mjla8hbkb5mssprg3gsq1v24s8m8nyk3hx5z7aaa1yr756";
+      };
+
+      beamDeps = [ hackney ];
+    };
+
+    ueberauth = buildMix rec {
+      name = "ueberauth";
+      version = "0.10.5";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1qf97azn8064ymawfm58p2bqpmrigipr4fs5xp3jb8chshqizz9y";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    ueberauth_cas = buildMix rec {
+      name = "ueberauth_cas";
+      version = "2.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "08g3kdcx2zxv963nqvwspsh1pqiiclx4hrwsm82jyz11kqmsws2h";
+      };
+
+      beamDeps = [ httpoison sweet_xml ueberauth ];
+    };
+
+    ueberauth_discord = buildMix rec {
+      name = "ueberauth_discord";
+      version = "0.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1d4nk3hshwj1ybr5ywz95m6qrrp0f2jcnyjbvbmdqkdv3bwqxyfn";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_facebook = buildMix rec {
+      name = "ueberauth_facebook";
+      version = "0.10.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1a1pjlxa86v5kbqsxb7lcggbs45pq6380zpppy5dll0wdgbfb35z";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_github = buildMix rec {
+      name = "ueberauth_github";
+      version = "0.8.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1iiw953l4mk087r2jrpcnyqdmgv2n8cq5947f8fsbkrjkj3v42mf";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_gitlab_strategy = buildMix rec {
+      name = "ueberauth_gitlab_strategy";
+      version = "0.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "07h94s9fy46mqkn4g3wd62lkpgpjfcdk1cd60myc0qxh9dwjwvp8";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_google = buildMix rec {
+      name = "ueberauth_google";
+      version = "0.10.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "04js5qrydc7f3szphcwcccfsrrhyqqii5rhc4j8diqmmkms8gygw";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_keycloak_strategy = buildMix rec {
+      name = "ueberauth_keycloak_strategy";
+      version = "0.4.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "06r10w0azlpypjgggar1lf7h2yazn2dpyicy97zxkjyxgf9jfc60";
+      };
+
+      beamDeps = [ oauth2 ueberauth ];
+    };
+
+    ueberauth_twitter = buildMix rec {
+      name = "ueberauth_twitter";
+      version = "0.4.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0rb0n41s60b89385ggzvags3mxgbp4iv7gxympqpdyd3w6iqxjl3";
+      };
+
+      beamDeps = [ httpoison oauther ueberauth ];
+    };
+
+    unicode_util_compat = buildRebar3 rec {
+      name = "unicode_util_compat";
+      version = "0.7.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "08952lw8cjdw8w171lv8wqbrxc4rcmb3jhkrdb7n06gngpbfdvi5";
+      };
+
+      beamDeps = [];
+    };
+
+    unplug = buildMix rec {
+      name = "unplug";
+      version = "1.0.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0fkwkg6qm2lsvil8xdba9mmcgi5iw41w42dqhm72shdab1bshwfi";
+      };
+
+      beamDeps = [ plug ];
+    };
+
+    unsafe = buildMix rec {
+      name = "unsafe";
+      version = "1.0.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1rahpgz1lsd66r7ycns1ryz2qymamz1anrlps986900lsai2jxvc";
+      };
+
+      beamDeps = [];
+    };
+
+    vite_phx = buildMix rec {
+      name = "vite_phx";
+      version = "0.3.1";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0dn9bygcsjyxllw2cxm9iggmqjyx9i67dix2y07ljcd1jih75c88";
+      };
+
+      beamDeps = [ jason phoenix ];
+    };
+
+    websock = buildMix rec {
+      name = "websock";
+      version = "0.5.2";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "01gzcvz86x4hxk5d50qz38nkmi1fakyn5yw0m7gi6s6a5zi5spwj";
+      };
+
+      beamDeps = [];
+    };
+
+    websock_adapter = buildMix rec {
+      name = "websock_adapter";
+      version = "0.5.3";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "0g8djd6l1yq8s84y4r3938dczvjs6jgxjbdm0ah6wszqq4abirfb";
+      };
+
+      beamDeps = [ plug plug_cowboy websock ];
+    };
+
+    xml_builder = buildMix rec {
+      name = "xml_builder";
+      version = "2.2.0";
+
+      src = fetchHex {
+        pkg = "${name}";
+        version = "${version}";
+        sha256 = "1jb29bv6hgz7z2bdw119am6z17nkg1033936h4smsmhpp4pxarlx";
+      };
+
+      beamDeps = [];
+    };
+  };
+in self
+
diff --git a/pkgs/servers/mobilizon/package.json b/pkgs/servers/mobilizon/package.json
new file mode 100644
index 0000000000000..f9366270eee25
--- /dev/null
+++ b/pkgs/servers/mobilizon/package.json
@@ -0,0 +1,140 @@
+{
+  "name": "mobilizon",
+  "version": "3.1.3",
+  "private": true,
+  "scripts": {
+    "dev": "vite",
+    "preview": "vite preview",
+    "build": "yarn run build:assets && yarn run build:pictures",
+    "lint": "eslint --ext .ts,.vue --ignore-path .gitignore --fix src",
+    "format": "prettier .  --write",
+    "build:assets": "vite build",
+    "build:pictures": "bash ./scripts/build/pictures.sh",
+    "story:dev": "histoire dev",
+    "story:build": "histoire build",
+    "story:preview": "histoire preview",
+    "test": "vitest",
+    "coverage": "vitest run --coverage",
+    "prepare": "cd ../ && husky install"
+  },
+  "lint-staged": {
+    "**/*.{js,ts,vue}": [
+      "eslint --fix",
+      "prettier --write"
+    ]
+  },
+  "dependencies": {
+    "@absinthe/socket": "^0.2.1",
+    "@absinthe/socket-apollo-link": "^0.2.1",
+    "@apollo/client": "^3.3.16",
+    "@oruga-ui/oruga-next": "^0.6.0",
+    "@sentry/tracing": "^7.1",
+    "@sentry/vue": "^7.1",
+    "@tiptap/core": "^2.0.0-beta.41",
+    "@tiptap/extension-blockquote": "^2.0.0-beta.25",
+    "@tiptap/extension-bold": "^2.0.0-beta.24",
+    "@tiptap/extension-bubble-menu": "^2.0.0-beta.9",
+    "@tiptap/extension-bullet-list": "^2.0.0-beta.23",
+    "@tiptap/extension-document": "^2.0.0-beta.15",
+    "@tiptap/extension-dropcursor": "^2.0.0-beta.25",
+    "@tiptap/extension-gapcursor": "^2.0.0-beta.33",
+    "@tiptap/extension-heading": "^2.0.0-beta.23",
+    "@tiptap/extension-history": "^2.0.0-beta.21",
+    "@tiptap/extension-image": "^2.0.0-beta.6",
+    "@tiptap/extension-italic": "^2.0.0-beta.24",
+    "@tiptap/extension-link": "^2.0.0-beta.8",
+    "@tiptap/extension-list-item": "^2.0.0-beta.19",
+    "@tiptap/extension-mention": "^2.0.0-beta.42",
+    "@tiptap/extension-ordered-list": "^2.0.0-beta.24",
+    "@tiptap/extension-paragraph": "^2.0.0-beta.22",
+    "@tiptap/extension-placeholder": "^2.0.0-beta.199",
+    "@tiptap/extension-strike": "^2.0.0-beta.26",
+    "@tiptap/extension-text": "^2.0.0-beta.15",
+    "@tiptap/extension-underline": "^2.0.0-beta.7",
+    "@tiptap/pm": "^2.0.0-beta.220",
+    "@tiptap/suggestion": "^2.0.0-beta.195",
+    "@tiptap/vue-3": "^2.0.0-beta.96",
+    "@vue-a11y/announcer": "^2.1.0",
+    "@vue-a11y/skip-to": "^2.1.2",
+    "@vue-leaflet/vue-leaflet": "^0.10.1",
+    "@vue/apollo-composable": "^4.0.0-beta.5",
+    "@vue/compiler-sfc": "^3.2.37",
+    "@vueuse/core": "^10.0.2",
+    "@vueuse/head": "^1.0",
+    "@vueuse/router": "^10.0.2",
+    "apollo-absinthe-upload-link": "^1.5.0",
+    "autoprefixer": "^10",
+    "blurhash": "^2.0.0",
+    "date-fns": "^2.16.0",
+    "date-fns-tz": "^2.0.0",
+    "floating-vue": "^2.0.0-beta.17",
+    "graphql": "^15.8.0",
+    "graphql-tag": "^2.10.3",
+    "hammerjs": "^2.0.8",
+    "intersection-observer": "^0.12.0",
+    "jwt-decode": "^3.1.2",
+    "leaflet": "^1.4.0",
+    "leaflet.locatecontrol": "^0.79",
+    "leaflet.markercluster": "^1.5.3",
+    "lodash": "^4.17.11",
+    "ngeohash": "^0.6.3",
+    "p-debounce": "^4.0.0",
+    "phoenix": "^1.6",
+    "postcss": "^8",
+    "register-service-worker": "^1.7.2",
+    "sanitize-html": "^2.5.3",
+    "tailwindcss": "^3",
+    "tippy.js": "^6.2.3",
+    "unfetch": "^5.0.0",
+    "vue": "^3.2.37",
+    "vue-i18n": "9",
+    "vue-material-design-icons": "^5.1.2",
+    "vue-matomo": "^4.1.0",
+    "vue-plausible": "^1.3.1",
+    "vue-router": "4",
+    "vue-scrollto": "^2.17.1",
+    "vue-use-route-query": "^1.1.0",
+    "zhyswan-vuedraggable": "^4.1.3"
+  },
+  "devDependencies": {
+    "@histoire/plugin-vue": "^0.16.1",
+    "@playwright/test": "^1.25.1",
+    "@rushstack/eslint-patch": "^1.1.4",
+    "@tailwindcss/forms": "^0.5.2",
+    "@tailwindcss/typography": "^0.5.4",
+    "@types/hammerjs": "^2.0.41",
+    "@types/leaflet": "^1.5.2",
+    "@types/leaflet.locatecontrol": "^0.74",
+    "@types/leaflet.markercluster": "^1.5.1",
+    "@types/lodash": "^4.14.141",
+    "@types/ngeohash": "^0.6.2",
+    "@types/phoenix": "^1.5.2",
+    "@types/sanitize-html": "^2.5.0",
+    "@vitejs/plugin-vue": "^4.0.0",
+    "@vitest/coverage-c8": "^0.32.2",
+    "@vitest/ui": "^0.32.2",
+    "@vue/eslint-config-prettier": "^7.0.0",
+    "@vue/eslint-config-typescript": "^11.0.0",
+    "@vue/test-utils": "^2.0.2",
+    "eslint": "^8.21.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-import": "^2.20.2",
+    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-vue": "^9.3.0",
+    "flush-promises": "^1.0.2",
+    "histoire": "^0.16.1",
+    "husky": "^8.0.3",
+    "jsdom": "^22.0.0",
+    "lint-staged": "^13.2.2",
+    "mock-apollo-client": "^1.1.0",
+    "prettier": "^2.2.1",
+    "prettier-eslint": "^15.0.1",
+    "rollup-plugin-visualizer": "^5.7.1",
+    "sass": "^1.34.1",
+    "typescript": "~5.1.3",
+    "vite": "^4.0.4",
+    "vite-plugin-pwa": "^0.16.4",
+    "vitest": "^0.32.2",
+    "vue-i18n-extract": "^2.0.4"
+  }
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 29c842221f817..b7949ad5ab036 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -6001,6 +6001,10 @@ with pkgs;
 
   moar = callPackage ../tools/misc/moar { };
 
+  mobilizon = callPackage ../servers/mobilizon {
+    mobilizon-frontend = callPackage ../servers/mobilizon/frontend.nix { };
+  };
+
   molly-brown = callPackage ../servers/gemini/molly-brown { };
 
   monetdb = callPackage ../servers/sql/monetdb { };