{
  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"; }
              ];
          };

      };


    };
}