{
  description = "Mobilizon fork for potsda.mn";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    nix-filter.url = "github:numtide/nix-filter";
    napalm.url = "github:nix-community/napalm";
    napalm.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs =
    {
      self,
      nixpkgs,
      nix-filter,
      napalm,
    }:
    let
      forAllSystems =
        f:
        nixpkgs.lib.genAttrs [
          "x86_64-linux"
          "aarch64-linux"
        ] (system: f system);
      nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
      filter = nix-filter.lib;
    in
    {

      packages = forAllSystems (
        system:
        let
          pkgs = nixpkgsFor.${system};
          # Directories that are neither needed for building the frontend nor the backend.
          # For better build caching.
          unrelatedDirs = [
            "flake.lock"
            (filter.matchExt "nix")
            "docs"
            "docker"
            "docker-compose.test.yml"
            "docker-compose.yml"
            "generate-test-data"
          ];
        in
        {
          mobilizon = pkgs.callPackage ./. {
            src = filter {
              root = ./.;
              exclude = [
                "src"
                (filter.matchExt "js")
                (filter.matchExt "ts")
                (filter.matchExt "json")
                "tests"
                "scripts"
                "public"
              ] ++ unrelatedDirs;
            };
            src-config = ./config;
            mobilizon-js = self.packages."${system}".mobilizon-frontend;
          };

          mobilizon-frontend =
            let
              nodejs = pkgs.nodejs-18_x;
            in
            napalm.legacyPackages."${system}".buildPackage
              (filter {
                root = ./.;
                exclude = [
                  "lib"
                  "config"
                  "test"
                  "rel"
                  "support"
                ] ++ unrelatedDirs;
              })
              {
                inherit nodejs;
                nativeBuildInputs = [ pkgs.imagemagick ];
                npmCommands = [
                  "npm install"
                  "npm run build"
                ];
                # Keep this in sync with the content of ./patches/
                customPatchPackages = {
                  vue-i18n-extract."2.0.7" = pkgs: prev: {
                    preConfigure = ''
                      ${pkgs.git}/bin/git apply -p3 ${./patches/vue-i18n-extract+2.0.7.patch}
                    '';
                  };
                };
              };

          default = self.packages."${system}".mobilizon;

          # Update local Mobilizon definition
          update = pkgs.writeShellScriptBin "update" ''
            set -eou pipefail

            ${pkgs.mix2nix}/bin/mix2nix ./mix.lock > mix.nix
          '';
        }
      );

      devShells = forAllSystems (
        system:
        let
          pkgs = nixpkgsFor.${system};
          settingsFormat = pkgs.formats.elixirConf { };
          mobilizonConfig = settingsFormat.generate "runtime.exs" {
            ":mobilizon" = {
              "Mobilizon.Web.Endpoint" = {
                server = true;
                url.host = "mobilizon.dev";
                http = {
                  ip = settingsFormat.lib.mkTuple [
                    0
                    0
                    0
                    0
                    0
                    0
                    0
                    1
                  ];
                  port = 4000;
                };
                secret_key_base = "2q/l1WDx3RQQy7gZ1k001//6nc66moWUEJQyGuMK/z3zPLYW6FYtIgCkUzGP0+X/";
              };
              "Mobilizon.Web.Auth.Guardian" = {
                secret_key = "N8x7/tf0kInLFS2poO22g6OGPiMjSrDEhmk29nFqV35q7hQ0DtBt/cRYCsqBNp2L";
              };
              ":instance" = {
                name = "Mobilizon";
                description = "Change this to a proper description of your instance";
                hostname = "mobilizon.dev";
                registrations_open = true;
                email_from = "noreply@mobilizon.dev";
                email_reply_to = "noreply@mobilizon.dev";
              };
              "Mobilizon.Storage.Repo" = {
                adapter = settingsFormat.lib.mkAtom "Ecto.Adapters.Postgres";
                pool_size = 10;
                username = "mobilizon";
                database = "mobilizon";
                socket_dir = "/var/run/postgresql";
              };
            };
          };
        in
        {
          default = pkgs.mkShell {
            MIX_ENV = "dev";
            PGUSER = "mobilizon";
            PGDATABASE = "mobilizon";
            buildInputs = with pkgs; [
              elixir
              mix2nix
              cmake
              imagemagick
              nodejs
              inotify-tools
              (pkgs.writeShellApplication {
                name = "build";
                runtimeInputs = [
                  elixir
                  nodejs
                ];
                text = ''
                  mix deps.get
                  mix deps.compile
                  mix phx.digest
                  npm install
                  npm run build
                '';
              })
              (pkgs.writeShellApplication {
                name = "setup";
                runtimeInputs = [
                  elixir
                  postgresql
                ];
                text = ''
                  cat ${mobilizonConfig} > config/runtime.exs
                  # We assume the database already exists
                  sudo -u postgres psql -d mobilizon << SQL
                    create extension if not exists postgis;
                    create extension if not exists unaccent;
                    create extension if not exists pg_trgm;
                  SQL
                  mix ecto.migrate
                '';
              })
              (pkgs.writeShellApplication {
                name = "start";
                runtimeInputs = [ elixir ];
                text = ''
                  mix phx.server
                '';
              })
              (pkgs.writeShellApplication {
                name = "clean";
                runtimeInputs = [ postgresql ];
                text = ''
                  rm -rf deps/ _build/ node_modules/
                  sudo -u postgres psql  -c "DROP DATABASE mobilizon;"
                  sudo systemctl restart postgresql.service
                '';
              })
            ];
          };
        }
      );

      nixosModules.devSetup =
        {
          config,
          lib,
          pkgs,
          ...
        }:
        let
          cfg = config.mobilizonDevEnvironment;
        in
        {

          options.mobilizonDevEnvironment = {
            enable = lib.mkEnableOption (lib.mdDoc "development environment for Mobilizon");
            user = lib.mkOption {
              type = lib.types.str;
              description = "Unix user that runs the backend application to connect to the database";
            };
          };

          config = lib.mkIf cfg.enable {
            services.postgresql = {
              enable = true;
              ensureDatabases = [ "mobilizon" ];
              ensureUsers = [
                {
                  name = "mobilizon";
                  ensureDBOwnership = true;
                }
              ];
              extraPlugins = with config.services.postgresql.package.pkgs; [ postgis ];
              identMap = ''
                map-mobilizon ${cfg.user} mobilizon
              '';
              authentication = ''
                local all mobilizon ident map=map-mobilizon
              '';
            };

          };

        };

      overlays.default = final: prev: { inherit (self.packages."${prev.system}") mobilizon; };

      checks = forAllSystems (system: {
        inherit (self.packages.${system}) mobilizon update;
        nixosTest =
          let
            pkgsMobilizon = import nixpkgs {
              inherit system;
              overlays = [ self.overlays.default ];
            };
            certs = import "${nixpkgs}/nixos/tests/common/acme/server/snakeoil-certs.nix";
            test = import ./integration-test.nix { inherit certs; };
          in
          pkgsMobilizon.nixosTest test;
      });

      lib = {
        # Patch the logos in the source tree of a mobilizon-frontend package before building.
        # Can be used to construct the argument for .overrideAttrs on mobilizon-frontend.
        mobilizonLogosOverride =
          icons:
          let
            inherit (icons) logo favicon;
          in
          old: {
            postPatch =
              ''
                cp '${logo}' src/assets/logo.svg

                magick convert \
                  -resize x16 \
                  -gravity center \
                  -crop 16x16+0+0 \
                  -flatten \
                  -colors 256 \
                  '${favicon}' \
                  public/img/icons/favicon-16x16.png

                magick convert \
                  -resize x32 \
                  -gravity center \
                  -crop 32x32+0+0 \
                  -flatten \
                  -colors 256 \
                  '${favicon}' \
                  public/img/icons/favicon-32x32.png


                magick convert \
                  -resize x16 \
                  -gravity center \
                  -crop 16x16+0+0 \
                  -flatten \
                  -colors 256 \
                  '${favicon}' \
                  favicon-16x16.ico

                magick convert \
                  -resize x32 \
                  -gravity center \
                  -crop 32x32+0+0 \
                  -flatten \
                  -colors 256 \
                  '${favicon}' \
                  favicon-32x32.ico

                magick convert \
                  -resize x48 \
                  -gravity center \
                  -crop 48x48+0+0 \
                  -flatten \
                  -colors 256 \
                  '${favicon}' \
                  favicon-48x48.ico

                magick convert \
                  favicon-16x16.ico \
                  favicon-32x32.ico \
                  favicon-48x48.ico \
                  public/favicon.ico

                rm favicon-16x16.ico favicon-32x32.ico favicon-48x48.ico

                cp '${favicon}' public/img/icons/favicon.svg
                cp '${favicon}' public/img/icons/safari-pinned-tab.svg

                magick convert \
                  '${favicon}' \
                  -gravity center \
                  -extent 630x350 \
                  public/img/mobilizon_default_card.png

                magick convert \
                  -background '#e08c96' \
                  '${logo}' \
                  -resize 366x108 \
                  public/img/mobilizon_logo.png

              ''
              +
                nixpkgs.lib.concatMapStrings
                  (
                    { resize, filename }:
                    ''
                      magick convert \
                        -resize x${resize} \
                        '${favicon}' \
                        public/img/icons/${filename}

                    ''
                  )
                  [
                    {
                      resize = "180";
                      filename = "apple-touch-icon.png";
                    }
                    {
                      resize = "180";
                      filename = "apple-touch-icon-180x180.png";
                    }
                    {
                      resize = "152";
                      filename = "apple-touch-icon-152x152.png";
                    }
                    {
                      resize = "120";
                      filename = "apple-touch-icon-120x120.png";
                    }
                    {
                      resize = "76";
                      filename = "apple-touch-icon-76x76.png";
                    }
                    {
                      resize = "60";
                      filename = "apple-touch-icon-60x60.png";
                    }
                    {
                      resize = "192";
                      filename = "android-chrome-192x192.png";
                    }
                    {
                      resize = "512";
                      filename = "android-chrome-512x512.png";
                    }
                    {
                      resize = "192";
                      filename = "android-chrome-maskable-192x192.png";
                    }
                    {
                      resize = "512";
                      filename = "android-chrome-maskable-512x512.png";
                    }
                    {
                      resize = "128";
                      filename = "badge-128x128.png";
                    }
                    {
                      resize = "144";
                      filename = "icon-144x144.png";
                    }
                    {
                      resize = "168";
                      filename = "icon-168x168.png";
                    }
                    {
                      resize = "256";
                      filename = "icon-256x256.png";
                    }
                    {
                      resize = "48";
                      filename = "icon-48x48.png";
                    }
                    {
                      resize = "72";
                      filename = "icon-72x72.png";
                    }
                    {
                      resize = "96";
                      filename = "icon-96x96.png";
                    }
                    {
                      resize = "144";
                      filename = "msapplication-icon-144x144.png";
                    }
                    {
                      resize = "150";
                      filename = "mstile-150x150.png";
                    }
                    {
                      resize = "192";
                      filename = "android-chrome-192x192.png";
                    }
                    {
                      resize = "512";
                      filename = "android-chrome-512x512.png";
                    }
                    {
                      resize = "192";
                      filename = "android-chrome-maskable-192x192.png";
                    }
                    {
                      resize = "512";
                      filename = "android-chrome-maskable-512x512.png";
                    }
                  ];
          };

      };

      formatter = forAllSystems (system: nixpkgsFor.${system}.nixfmt-rfc-style);

    };
}