{
  description = "pbsds' system/home flake";

  inputs = {
    # https://github.com/nixos/nixpkgs
    nixpkgs-test.url = "github:NixOS/nixpkgs/refs/pull/295155/head"; # for testing
    nixpkgs-edge.url = "github:NixOS/nixpkgs/nixos-unstable"; # "edge" is four letters
    nixpkgs-2405.url = "github:NixOS/nixpkgs/nixos-24.05";
    nixpkgs-2311.url = "github:NixOS/nixpkgs/nixos-23.11";
    nixpkgs-2305.url = "github:NixOS/nixpkgs/nixos-23.05";
    nixpkgs-2211.url = "github:NixOS/nixpkgs/nixos-22.11"; # for old docs
    nixpkgs-2205.url = "github:NixOS/nixpkgs/nixos-22.05"; # for old docs
    nixpkgs-2111.url = "github:NixOS/nixpkgs/nixos-21.11"; # for old docs
    nixpkgs-2105.url = "github:NixOS/nixpkgs/nixos-21.05"; # for old docs
    nixpkgs-2009.url = "github:NixOS/nixpkgs/nixos-20.09"; # for old docs
    nixpkgs-2003.url = "github:NixOS/nixpkgs/nixos-20.03"; # for old docs
    nixpkgs-1909.url = "github:NixOS/nixpkgs/nixos-19.09"; # for old docs
    nixpkgs-1909.flake = false; # Earlier versions are not flake-pure

    # https://github.com/nix-community/home-manager
    home-manager-edge.url = "github:nix-community/home-manager/master";
    home-manager-edge.inputs.nixpkgs.follows = "nixpkgs-edge";
    home-manager-2405.url = "github:nix-community/home-manager/release-24.05";
    home-manager-2405.inputs.nixpkgs.follows = "nixpkgs-2405";
    home-manager-2311.url = "github:nix-community/home-manager/release-23.11";
    home-manager-2311.inputs.nixpkgs.follows = "nixpkgs-2311";
    home-manager-2305.url = "github:nix-community/home-manager/release-23.05";
    home-manager-2305.inputs.nixpkgs.follows = "nixpkgs-2305";

    # https://github.com/nix-community/nix-index-database
    nix-index-database.url = "github:Mic92/nix-index-database";
    nix-index-database.inputs.nixpkgs.follows = "nixpkgs-2311"; # only used for .packages

    # https://github.com/NixOS/nixos-hardware
    nixos-hardware.url = "github:NixOS/nixos-hardware";

    # https://github.com/tfc/nspawn-nixos
    nixos-nspawn.url = "github:tfc/nspawn-nixos";
    nixos-nspawn.flake = false; # we don't use it /shrug

    # https://github.com/wamserma/flake-programs-sqlite
    #flake-programs-sqlite-2311.url = "github:wamserma/flake-programs-sqlite";
    #flake-programs-sqlite-2311.inputs.nixpkgs.follows = "nixpkgs-2311";

    # https://github.com/nix-community/nixos-generators
    nixos-generators-2405.url = "github:nix-community/nixos-generators";
    nixos-generators-2405.inputs.nixpkgs.follows = "nixpkgs-2405";

    # https://github.com/Mic92/sops-nix
    sops-nix-edge.url = "github:Mic92/sops-nix";
    sops-nix-edge.inputs.nixpkgs.follows = "nixpkgs-edge";
    sops-nix-edge.inputs.nixpkgs-stable.follows = "nixpkgs-2405";
    sops-nix-2405.url = "github:Mic92/sops-nix";
    sops-nix-2405.inputs.nixpkgs.follows = "nixpkgs-2405";
    sops-nix-2405.inputs.nixpkgs-stable.follows = "nixpkgs-2405";
    sops-nix-2311.url = "github:Mic92/sops-nix";
    sops-nix-2311.inputs.nixpkgs.follows = "nixpkgs-2311";
    sops-nix-2311.inputs.nixpkgs-stable.follows = "nixpkgs-2311";
    sops-nix-2305.url = "github:Mic92/sops-nix";
    sops-nix-2305.inputs.nixpkgs.follows = "nixpkgs-2305";
    sops-nix-2305.inputs.nixpkgs-stable.follows = "nixpkgs-2305";

    ## https://github.com/h7x4/maunium-stickerpicker-nix
    #maunium-stickerpicker-nix.url = "github:h7x4/maunium-stickerpicker-nix";
    #maunium-stickerpicker-nix.inputs.nixpkgs.follows = "nixpkgs-2311";

    /** /
    https://willbush.dev/blog/impermanent-nixos/
    matrix-next.url = "github:dali99/nixos-matrix-modules"; # see https://git.pvv.ntnu.no/Drift/pvv-nixos-config/src/main/flake.nix
    #https://github.com/considerate/nixos-odroidhc4
    #https://cyberchaos.dev/cyberchaoscreatures/musl-nixos/
    #https://github.com/numtide/system-manager
    nix-vscode-extensions.url = "github:nix-community/nix-vscode-extensions"
    #https://github.com/numtide/nixpkgs-unfree # has a cache
    #https://github.com/matthewbauer/nixiosk

    # https://github.com/cachix/pre-commit-hooks.nix
    inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
    inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs-edge";
    inputs.pre-commit-hooks.inputs.nixpkgs-stable.follows = "nixpkgs-2311";
    /**/

    #pbsds-papers.url = "git+ssh://git@github.com/pbsds/papers.git";
  };

  nixConfig.extra-substituters = [
    "https://cuda-maintainers.cachix.org"
    "https://nix-community.cachix.org"
    "https://nixos-rocm.cachix.org"
    "https://nixpkgs-unfree.cachix.org"
    "https://numtide.cachix.org"
  ];
  nixConfig.extra-trusted-public-keys = [
    "cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E="
    "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
    "nixos-rocm.cachix.org-1:VEpsf7pRIijjd8csKjFNBGzkBqOmw8H9PRmgAq14LnE="
    "nixpkgs-unfree.cachix.org-1:hqvoInulhbV4nJ9yJOEr+4wxhDV4xq2d1DK7S6Nj6rs="
    "numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE="
  ];

  outputs = {
    self,
    nixos-hardware,
    nixos-nspawn,
    nixos-generators-2405,
    ...
  } @ inputs':
  let
    inputs-edge = inputs' // {
      nixpkgs = inputs'.nixpkgs-edge;
      unstable = inputs'.nixpkgs-edge;
      home-manager = inputs'.home-manager-edge;
      sops-nix = inputs'.sops-nix-edge;
    };
    inputs-2405 = inputs' // {
      nixpkgs = inputs'.nixpkgs-2405;
      unstable = inputs'.nixpkgs-edge;
      home-manager = inputs'.home-manager-2405;
      sops-nix = inputs'.sops-nix-2405;
    };
    inputs-2311 = inputs' // {
      nixpkgs = inputs'.nixpkgs-2311;
      unstable = inputs'.nixpkgs-2405;
      home-manager = inputs'.home-manager-2311;
      sops-nix = inputs'.sops-nix-2311;
    };
    inputs-2305 = inputs' // {
      nixpkgs = inputs'.nixpkgs-2305;
      unstable = inputs'.nixpkgs-2311;
      home-manager = inputs'.home-manager-2305;
      sops-nix = inputs'.sops-nix-2305;
    };
    inputs-2211 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2211; };
    inputs-2205 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2205; };
    inputs-2111 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2111; };
    inputs-2105 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2105; };
    inputs-2009 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2009; };
    inputs-2003 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-2003; };
    inputs-1909 = inputs-2305 // { nixpkgs = inputs'.nixpkgs-1909; };
    inputs-test = inputs-edge // { nixpkgs = inputs'.nixpkgs-test; unstable = inputs'.nixpkgs-test; };

    forSystems = systems: f: inputs-edge.nixpkgs.lib.genAttrs systems (system: f rec {
      inherit system;
      inputs = inputs-edge;
      pkgs = inputs.nixpkgs.legacyPackages.${system};
      lib  = inputs.nixpkgs.legacyPackages.${system}.lib;
    });
    forAllSystems = forSystems [
      "x86_64-linux"
      "aarch64-linux"
      #"riscv64-linux"
    ];

    mkModule = extra-modules: domain: system: inputs: stateVersion: modules: hostname: ({ lib, ... }: {
      system.stateVersion = lib.mkDefault stateVersion; # TODO: home-manager

      imports = let ifExists = p: if builtins.pathExists p then p else {}; in [
        ./base.nix
        "${self}/hosts/${hostname}/configuration.nix"
        inputs.sops-nix.nixosModules.sops
        inputs.home-manager.nixosModule
        #inputs.nix-index-database.nixosModules.nix-index # TODO: fix?
      ] ++ modules ++ extra-modules;
      #++ inputs.flake-programs-sqlite.nixosModules.programs-sqlite; # TODO: make work

      sops.defaultSopsFile = ./secrets/default.yaml;
      #sops.defaultSopsFile = lib.mkIf (builtins.pathExists ./secrets/${hostname}.yaml) ./secrets/${hostname}.yaml;
      #sops.secrets = let # TODO: importYAML does not exist
      #  file = ./secrets/${hostname}.yaml;
      #  exists = builtins.pathExists file;
      #  yaml = lib.removeAttrs (lib.importYAML file) ["sops"];
      #  secrets = lib.attrNames yaml; # TODO: recurse
      #in
      #  if !exists then {} else lib.mkMerge (lib.forEach secrets (secret:
      #    lib.mkIf (config.sops.secrets ? secret) {
      #      "${secret}".sopsFile = file;
      #    }
      #  ));
      sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
      sops.age.keyFile = "/var/lib/sops-nix/key.txt";
      sops.age.generateKey = true;

      home-manager.useGlobalPkgs = true; # go brrr, reuse overrides
      home-manager.extraSpecialArgs = {
        inherit inputs;
      };
      home-manager.sharedModules = [
        inputs.sops-nix.homeManagerModules.sops
        inputs.nix-index-database.hmModules.nix-index
      ];

      # still needed even if using networkd
      networking.hostName = hostname;
      networking.domain   = domain;
      networking.search = [ domain ];

      nixpkgs.overlays = [
        self.overlays.pbsdspkgs
        (final: prev: {
          unstable = import inputs.unstable {
            inherit system;
            config = final.pkgs.config;
          };
        })
      ];

      # This makes commandline tools like 'nix run nixpkgs#hello'
      # and 'nix-shell -p hello' use the same channel as system was built with
      nix.registry.nixpkgs.flake = inputs.nixpkgs;
      nix.registry.nixpkgs-unstable.flake = inputs.unstable;
      nix.nixPath = [
        "nixpkgs=${inputs.nixpkgs}"
        "nixpkgs-unstable=${inputs.unstable}"
      ];
    });
    mkConfig = extra-modules: domain: system: inputs: stateVersion: modules: hostname: inputs.nixpkgs.lib.nixosSystem {
      inherit system;
      specialArgs = {
        inherit inputs;
      };
      modules = [ (mkModule extra-modules domain system inputs stateVersion modules hostname) ];
    };
    mkReport = extra-modules: domain: system: inputs: stateVersion: modules: hostname: let
      nixos = mkConfig extra-modules domain system inputs stateVersion modules hostname;
      cfg = nixos.config;
      inherit (nixos.pkgs) lib;
      # TODO: make it work, is it faster?
      #inherit (inputs.nixpkgs.legacyPackages.${system}) lib;
      #nixos = lib.evalModules { modules = [ (mkModule (extra-modules ++ { _module.check = false; }) domain system inputs stateVersion modules hostname) ]; };
      #cfg = nixos.config;
    in {
      inherit system; # TODO: cross system
      inherit (cfg.boot.binfmt) emulatedSystems;
      #inherit (cfg.system.build.toplevel) outPath;
      inherit (cfg.networking) fqdn;
      inherit (cfg.networking.firewall) allowedTCPPorts allowedUDPPorts;
      buildMachines = lib.forEach cfg.nix.buildMachines (buildMachine: buildMachine.hostName);
      users = lib.pipe cfg.users.users [
        (lib.filterAttrs  (uname: user: user.isNormalUser))
        (builtins.mapAttrs (uname: user: {
          authorizedKeys = lib.forEach user.openssh.authorizedKeys.keys (key: builtins.concatStringsSep " " [
            (builtins.elemAt (lib.splitString " " key) 0)
            "..."
            (builtins.elemAt (lib.splitString " " key) 2)
          ]);
        }))
      ];
      bootloader = if cfg.boot.loader.grub.enable then "grub"
        else if cfg.boot.loader.systemd-boot.enable then "systemd-boot"
        else null;
      mounts = lib.pipe cfg.fileSystems [
        (lib.filterAttrs (mount: fs: fs.fsType != "nfs"))
        (lib.mapAttrs (mount: fs: "${fs.fsType}://${fs.device}"))
      ];
      nginx-vhosts = lib.pipe cfg.services.nginx.virtualHosts [
        #(lib.filterAttrs (domain: vhost: )
        (lib.mapAttrs (domain: vhost: vhost.serverAliases or []))
      ];
    };
    mkHosts = mk: let
      ls = imports: { inherit imports; };
      hw = nixos-hardware.nixosModules;
      #vf = nixos-vf2.nixosModules;
      amd         = ls [ hw.common-pc hw.common-pc-ssd hw.common-cpu-amd ];
      intel       = ls [ hw.common-pc hw.common-pc-ssd hw.common-cpu-intel ./hardware/gpu/intel.nix ];
      intel-novga = ls [ hw.common-pc hw.common-pc-ssd hw.common-cpu-intel-cpu-only ];
      cuda        = ls [ ./hardware/gpu/cuda.nix hw.common-gpu-nvidia-nonprime ];
      cuda-prime  = ls [ ./hardware/gpu/cuda.nix hw.common-gpu-nvidia ];
      rocm        = ls [ ./hardware/gpu/rocm.nix hw.common-gpu-amd ];
      nspawn      = ls [ "${nixos-nspawn}/nspawn-image.nix" { boot.isContainer = true; } ];
      hidpi       = hw.common-hidpi;
      p1005       = ./hardware/printer/hp-laserjet-p1005.nix;
      au          = ./profiles/auto-upgrade.nix;
      ts          = ./profiles/tailscale.nix;
      #rb          = ./profiles/known-hosts.nix; # TODO
      nixld       = ./profiles/nix-ld.nix;
      dns64       = { config, ... }: {
        networking.nameservers = [ "2001:700:1:11::2:51" ]; # dns64.uninett.no
        networking.networkmanager.enable = true;
        networking.networkmanager.insertNameservers = config.networking.nameservers; # https://github.com/NixOS/nixpkgs/issues/61230
        networking.resolvconf.enable = false;
      };
    in builtins.mapAttrs (hostname: curried: curried hostname) {
      #hostname        "domain"    "system"        inputs      "state" [ modules ... ]
      noximilien  = mk "pbsds.net" "x86_64-linux"  inputs-2405 "23.11" [ au ts intel ];
      brumlebasse = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au    amd nspawn ];
      nord        = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au ts intel-novga hw.common-gpu-intel-sandy-bridge rocm hidpi ];
      sopp        = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au ts nixld intel cuda p1005 ];
      bjarte      = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [    ts nixld intel hw.lenovo-thinkpad-x1-7th-gen ];
      bolle       = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au    dns64 intel ];
      eple        = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au ts dns64 intel rocm ];
      garp        = mk "pbsds.net" "x86_64-linux"  inputs-2405 "24.05" [ au    dns64 intel-novga cuda ];
      hasselknippe= mk "pbsds.net" "aarch64-linux" inputs-2405 "24.05" [    ts hw.pine64-pinebook-pro ];
      #gomperud smattkuken skrytebiffen skalkesnerken balleby bingus skjrlaltatjlstad
      #bergjlot snortheimsmoen ditlefsen skrukkerud podebusk zmaragd makrell alfnes blix urke pytte uddu imdorf rosenqvist
    };
  in {
    inputs = inputs';

    lib = {
      # pass
    } // forAllSystems ({ system, ... }: {
      # pass
    });

    nixosModules        = mkHosts (mkModule []);
    nixosConfigurations = mkHosts (mkConfig []);
    nixosReports        = mkHosts (mkReport []);

    overlays = {
      pbsdspkgs = pkgs: prev: let inherit (pkgs) lib; in {
        pbsds = lib.makeScope pkgs.newScope (pbsds: {
          # TODO: get faketty to work, ${expect}/bin/unbuffer is bad
          nixos-rebuild-nom = pkgs.writeScriptBin "nixos-rebuild" ''
            if test -t 1 && test -z "''${NIX_NO_NOM-}"; then
              exec ${lib.getExe pkgs.nixos-rebuild} -L "$@" |& ${lib.getExe pkgs.nix-output-monitor}
            else
              exec ${lib.getExe pkgs.nixos-rebuild} -L "$@"
            fi
          '';

          v4l2-play = pkgs.callPackage ./pkgs/v4l2-play {};
          device-mon = pkgs.callPackage ./pkgs/device-mon {};
          #pdoc-docs  = (pkgs.callPackage ./pkgs/pdocs.nix {}).pdocs;
          #pdoc3-docs = (pkgs.callPackage ./pkgs/pdocs.nix {}).pdocs3;
        });
      };
      wl-clipboard-timeout = import ./overlays/wl-clipboard-timeout.nix;
      default = self.overlays.pbsdspkgs;
    };

    packages = forAllSystems ({ inputs, pkgs, lib, ... }: let
      # TODO: by-name
      mk-nspawn-setup = hostname: # TODO: nspawn-tarball.nix populates /etc/nixos with junk
        (pkgs.callPackage ./pkgs/mk-nspawn-setup {})
        (mkHosts (mkConfig [ "${nixos-nspawn}/nspawn-tarball.nix" ])).${hostname};
      pbsdspkgs = lib.filterAttrs (name: value: lib.isDerivation value) (self.overlays.pbsdspkgs pkgs null).pbsds;
    in
     pbsdspkgs // {
      nspawn-setup-brumlebasse = mk-nspawn-setup "brumlebasse";
      image-brumlebasse-openstack = nixos-generators-2405.nixosGenerate {
        system = "x86_64-linux";
        specialArgs = { inherit inputs; };
        modules = [ (mkHosts (mkModule [])).brumlebasse ];
        format = "openstack";
      };
    });

    homeModules.jump = ./users/pbsds/modules/jump.nix;
    homeModules.micro = ./users/pbsds/modules/micro.nix;

    homeConfigurations = forAllSystems ({ system, ... }: let
      mkHome = username: homeDirectory: inputs: modules: inputs.home-manager.lib.homeManagerConfiguration {
        pkgs = inputs.nixpkgs.legacyPackages.${system};
        modules = modules ++ [{
          home = { inherit username homeDirectory; };
          imports = [
            inputs.sops-nix.homeManagerModules.sops
            inputs.nix-index-database.hmModules.nix-index
          ];
        }];
        extraSpecialArgs = {
          inherit inputs;
        };
      };
    in {
      # TODO: pvv
      pbsds            = mkHome "pbsds" "/home/pbsds" inputs-edge [ ./users/pbsds/home ];
      pbsds-2405       = mkHome "pbsds" "/home/pbsds" inputs-2405 [ ./users/pbsds/home ];
      pbsds-2311       = mkHome "pbsds" "/home/pbsds" inputs-2311 [ ./users/pbsds/home ];
      pbsds-2305       = mkHome "pbsds" "/home/pbsds" inputs-2305 [ ./users/pbsds/home ];
      pbsds-gnome      = mkHome "pbsds" "/home/pbsds" inputs-edge [ ./users/pbsds/home/gnome.nix ];
      pbsds-gnome-2405 = mkHome "pbsds" "/home/pbsds" inputs-2405 [ ./users/pbsds/home/gnome.nix ];
      pbsds-gnome-2311 = mkHome "pbsds" "/home/pbsds" inputs-2311 [ ./users/pbsds/home/gnome.nix ];
      pbsds-gnome-2305 = mkHome "pbsds" "/home/pbsds" inputs-2305 [ ./users/pbsds/home/gnome.nix ];
    });

    # TODO: use this to pull changes to repo
    homeFileMap = let
      inherit (inputs-edge.nixpkgs) lib;
      files = self.homeConfigurations.x86_64-linux.pbsds-gnome.config.home.file;
    in lib.pipe files [
      (lib.filterAttrs (k: v: lib.hasPrefix (toString self) (toString v.source)))
      (lib.mapAttrs' (k: v: lib.nameValuePair
        (lib.removePrefix "/home/pbsds" k)
        ("." + lib.removePrefix  (toString self) (toString v.source))
      ))
    ];

    devShells = forAllSystems ({ pkgs, system, ... }: let
      mkShell = packages: pkgs.mkShellNoCC { inherit packages; };
      envrc-pkgs = [
        self.packages.${system}.nixos-rebuild-nom
        pkgs.home-manager
        pkgs.nix-output-monitor
        pkgs.cachix
        pkgs.age
        pkgs.sops
        pkgs.ssh-to-age
        pkgs.just
        pkgs.gum
        pkgs.mprocs
      ];
    in {
      envrc-local  = mkShell envrc-pkgs;
      envrc-remote = mkShell (envrc-pkgs ++ [
        pkgs.remote-exec
        pkgs.yq
        pkgs.rsync
      ]);
      remoteenv = mkShell [
        self.packages.${system}.nixos-rebuild-nom
        pkgs.age
        pkgs.ssh-to-age
        pkgs.just
        pkgs.gum
      ];
    });

  };
}