{
  inputs = {
    disko = {
      url = "github:nix-community/disko";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    fieldpoc.url = "git+https://git.jalr.de/jalr/fieldpoc.git";

    flake-utils.url = "github:numtide/flake-utils";

    home-manager = {
      url = "github:nix-community/home-manager/release-25.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    impermanence.url = "github:nix-community/impermanence";

    nix-topology = {
      url = "github:oddlama/nix-topology";
      inputs = {
        flake-utils.follows = "flake-utils";
        nixpkgs.follows = "nixpkgs";
        pre-commit-hooks.follows = "nix-pre-commit-hooks";
      };
    };

    krops = {
      url = "github:Mic92/krops";
      inputs.flake-utils.follows = "flake-utils";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nix-pre-commit-hooks = {
      url = "github:cachix/git-hooks.nix/master";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";

    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = {
    self,
    flake-utils,
    home-manager,
    krops,
    nix-pre-commit-hooks,
    nixpkgs,
    ...
  } @ inputs:
    flake-utils.lib.eachDefaultSystem
    (system: let
      pkgs = import nixpkgs {inherit system;};
      inherit (pkgs) lib;
    in {
      checks = {
        pre-commit-check = nix-pre-commit-hooks.lib.${system}.run {
          src = ./.;
          excludes = [
            ".envrc"
          ];
          hooks = {
            alejandra.enable = true;
            black.enable = true;
            deadnix.enable = true;
            markdownlint = {
              enable = true;
              settings.configuration = {
                MD013.code_blocks = false; # Ignore line length in code blocks.
              };
            };
            shellcheck.enable = true;
            statix = {
              enable = true;
              settings.ignore = [".direnv"];
            };
          };
        };
      };

      packages.docs = pkgs.stdenvNoCC.mkDerivation {
        name = "infrastructure-documentation";
        src = builtins.path {
          name = "docs-src";
          path = ./.;
          filter = path: _type: let
            relPath = lib.removePrefix (toString ./.) path;
          in
            lib.hasPrefix "/docs" relPath
            || lib.hasPrefix "/assets" relPath
            || relPath == "/book.toml";
        };
        nativeBuildInputs = with pkgs; [
          mdbook
        ];
        buildPhase = "mdbook build -d $out";
        dontInstall = true;
      };

      devShells.default = pkgs.mkShell {
        buildInputs = with pkgs; [
          alejandra
          aws-vault
          awscli2
          black
          jq
          just
          markdownlint-cli2
          mdbook
          opentofu
          shellcheck
          sops
          ssh-to-age
          (
            writeShellScriptBin "repl" ''
              exec nix repl --expr "
                let flake = builtins.getFlake \"$(git rev-parse --show-toplevel)\";
                in
                flake // {
                  lib = flake.inputs.nixpkgs.lib;
                  pkgs = flake.inputs.nixpkgs.legacyPackages."\''${builtins.currentSystem}";
                }
              "
            ''
          )
        ];

        shellHook = ''
          ${self.checks.${system}.pre-commit-check.shellHook}
        '';
      };

      apps =
        lib.mapAttrs
        (_name: program: {
          type = "app";
          program = toString program;
        })
        (flake-utils.lib.flattenTree {
          deploy = lib.recurseIntoAttrs (lib.mapAttrs
            (
              hostname: machine: let
                inherit (krops.packages.${system}) writeCommand;
                inherit (krops) lib;
              in
                writeCommand "deploy-${hostname}" {
                  target =
                    lib.mkTarget "root@${machine.config.deployment.targetHost}"
                    // {
                      extraOptions = [
                        # force allocation of tty to allow aborting with ^C and to show build progress
                        "-t"
                      ];
                    };
                  source = lib.evalSource (lib.singleton {
                    config.file = {
                      path = toString ./.;
                      useChecksum = true;
                    };
                  });
                  command = targetPath: ''
                    nixos-rebuild switch --flake ${targetPath}/config -L --keep-going
                  '';
                  force = true;
                }
            )
            self.nixosConfigurations);
        });
    })
    // {
      overlays.default = import ./pkgs inputs;

      nixosConfigurations = let
        domain = "lan.weinturm-open-air.net";
      in
        nixpkgs.lib.mapAttrs
        (hostname: {
          system,
          extraModules ? [],
          targetHost ? "${hostname}.${domain}",
        }:
          nixpkgs.lib.nixosSystem {
            inherit system;

            modules =
              [
                (./hosts + "/${hostname}/configuration.nix")

                ./modules

                {
                  _module.args = {inherit inputs domain;};
                }

                # deployment settings
                ({lib, ...}: {
                  options.deployment = {
                    targetHost = lib.mkOption {
                      type = lib.types.str;
                      readOnly = true;
                      internal = true;
                    };
                  };
                  config.deployment = {
                    inherit targetHost;
                  };
                })
              ]
              ++ [
                home-manager.nixosModules.home-manager
              ]
              ++ (with inputs; [
                disko.nixosModules.disko
                fieldpoc.nixosModules.default
                impermanence.nixosModules.impermanence
                sops-nix.nixosModules.sops
              ])
              ++ extraModules;
          })
        (import ./hosts inputs);
    };
}
