# A fork of https://github.com/NixOS/nix/blob/eeecbb9c364a29bbbde26aa41cc74204546950c0/docker.nix
{ system
, pkgsSource
, name
, nixosConfigurationSource
, podProfileDirPath
, podCommonDirPath ? ./common
, etcActivation ? false
, homeActivation ? false
, channelsList ? []
, extraSubstituters ? []
, extraTrustedPublicKeys ? []
, pkgs ? (import pkgsSource { inherit system; })
, lib ? pkgs.lib
, tag ? "latest"
, username ? "dev"
, userHome ? "/home/dev"
, mountingDir ? "${userHome}/src"
, userConfig ? {
    uid = 1000;
    shell = "${pkgs.zsh}/bin/zsh";
    home = userHome;
    gid = 1000;
    groups = [ username ];
    description = "A normal user";
  }
, groupConfig ? {
    gid = 1000;
  }
, maxLayers ? 100
, nixos ? (import  "${pkgsSource}/nixos" {
    inherit system;
    configuration = import nixosConfigurationSource;
  })
, flake-registry ? (pkgs.formats.json { }).generate "flake-registry.json" {
    version = 2;
    flakes = pkgs.lib.mapAttrsToList (n: v: { inherit (v) from to exact; }) ({
      nixos = {
        from = { type = "indirect"; id = "nixos"; };
        to = pkgs.path;
        exact = true;
      };
      nixpkgs = {
        from = { type = "indirect"; id = "nixpkgs"; };
        to = pkgs.path;
        exact = true;
      };
    });
  }
}:
let
  defaultPkgs = with pkgs; [
    nix
    bashInteractive
    coreutils-full
    gnutar
    gzip
    gnugrep
    which
    curl
    less
    wget
    man
    cacert.out
    findutils
    xz
    nixos.config.system.path
  ];

  users = {

    root = {
      uid = 0;
      shell = "${pkgs.zsh}/bin/zsh";
      home = "/root";
      gid = 0;
      groups = [ "root" ];
      description = "System administrator";
    };

    ${username} = userConfig;

    nobody = {
      uid = 65534;
      shell = "${pkgs.shadow}/bin/nologin";
      home = "/var/empty";
      gid = 65534;
      groups = [ "nobody" ];
      description = "Unprivileged account (don't use!)";
    };

  } // lib.listToAttrs (
    map
      (
        n: {
          name = "nixbld${toString n}";
          value = {
            uid = 30000 + n;
            gid = 30000;
            groups = [ "nixbld" ];
            description = "Nix build user ${toString n}";
          };
        }
      )
      (lib.lists.range 1 32)
  );

  groups = {
    root.gid = 0;
    ${username} = groupConfig;
    nixbld.gid = 30000;
    nobody.gid = 65534;
  };

  userToPasswd = (
    k:
    { uid
    , gid ? 65534
    , home ? "/var/empty"
    , description ? ""
    , shell ? "/bin/false"
    , groups ? [ ]
    }: "${k}:x:${toString uid}:${toString gid}:${description}:${home}:${shell}"
  );
  passwdContents = (
    lib.concatStringsSep "\n"
      (lib.attrValues (lib.mapAttrs userToPasswd users))
  );

  userToShadow = k: { ... }: "${k}:!:1::::::";
  shadowContents = (
    lib.concatStringsSep "\n"
      (lib.attrValues (lib.mapAttrs userToShadow users))
  );

  # Map groups to members
  # {
  #   group = [ "user1" "user2" ];
  # }
  groupMemberMap = (
    let
      # Create a flat list of user/group mappings
      mappings = (
        builtins.foldl'
          (
            acc: user:
              let
                groups = users.${user}.groups or [ ];
              in
              acc ++ map
                (group: {
                  inherit user group;
                })
                groups
          )
          [ ]
          (lib.attrNames users)
      );
    in
    (
      builtins.foldl'
        (
          acc: v: acc // {
            ${v.group} = acc.${v.group} or [ ] ++ [ v.user ];
          }
        )
        { }
        mappings)
  );

  groupToGroup = k: { gid }:
    let
      members = groupMemberMap.${k} or [ ];
    in
    "${k}:x:${toString gid}:${lib.concatStringsSep "," members}";
  groupContents = (
    lib.concatStringsSep "\n"
      (lib.attrValues (lib.mapAttrs groupToGroup groups))
  );

  defaultNixConf = {
    sandbox = "false";
    build-users-group = "nixbld";
    trusted-public-keys = [
      "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
    ] ++ extraTrustedPublicKeys;
    experimental-features = [ "nix-command" "flakes" ];
    substituters = [ "https://cache.nixos.org" ] ++ extraSubstituters;
  };

  nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v:
    let
      vStr = if builtins.isList v then lib.concatStringsSep " " v else v;
    in
      "${n} = ${vStr}") defaultNixConf)) + "\n";

  endpointScript = ''
    #!${pkgs.bashInteractive}/bin/bash

    cd /nix/var/nix/profiles/default/bin/
    nix-daemon --daemon &> /dev/null &

    tail -f /dev/null
  '';

  userGroupIds = "${toString users.${username}.uid}:${toString groups.${username}.gid}";

  copyToNixStore = dirname: directory: (pkgs.runCommand dirname {} ''
    mkdir $out
    find ${directory} -type f | while read file; do
      dir=''$(dirname $file)
      mkdir -p $out"''${dir#${directory}}"
      cp $file "$out''${file#${directory}}"
    done
  '');
  podCommonPath = copyToNixStore "common" podCommonDirPath;
  podNixosPath = copyToNixStore "nixos" podProfileDirPath;

  baseSystem =
    let
      nixpkgs = pkgs.path;
      channel = pkgs.runCommand "channel-nixos" { } ''
        mkdir $out
        ln -s ${nixpkgs} $out/nixpkgs
        echo "[]" > $out/manifest.nix
      '';
      nix-channels = pkgs.writeTextFile {
        name = "nix-channels";
        text = ''
          ${lib.concatStringsSep "\n"
            (builtins.map (channel: "${channel.url} ${channel.name}") channelsList)
          }
        '';
      };
      rootEnv = pkgs.buildPackages.buildEnv {
        name = "root-profile-env";
        paths = defaultPkgs;
      };
      manifest = pkgs.buildPackages.runCommand "manifest.nix" { } ''
        cat > $out <<EOF
        [
        ${lib.concatStringsSep "\n" (builtins.map (drv: let
          outputs = drv.outputsToInstall or [ "out" ];
        in ''
          {
            ${lib.concatStringsSep "\n" (builtins.map (output: ''
              ${output} = { outPath = "${lib.getOutput output drv}"; };
            '') outputs)}
            outputs = [ ${lib.concatStringsSep " " (builtins.map (x: "\"${x}\"") outputs)} ];
            name = "${drv.name}";
            outPath = "${drv}";
            system = "${drv.system}";
            type = "derivation";
            meta = { };
          }
        '') defaultPkgs)}
        ]
        EOF
      '';
      profile = pkgs.buildPackages.runCommand "user-environment" { } ''
        mkdir $out
        cp -a ${rootEnv}/* $out/
        ln -s ${manifest} $out/manifest.nix
      '';
      flake-registry-path = if (flake-registry == null) then
        null
      else if (builtins.readFileType (toString flake-registry)) == "directory" then
        "${flake-registry}/flake-registry.json"
      else
        flake-registry;
    in
    pkgs.runCommand "base-system"
      {
        inherit passwdContents groupContents shadowContents nixConfContents endpointScript;
        passAsFile = [
          "passwdContents"
          "groupContents"
          "shadowContents"
          "nixConfContents"
          "endpointScript"
        ];
        allowSubstitutes = false;
        preferLocalBuild = true;
      } (''
      env
      set -x
      mkdir -p $out/etc

      mkdir -p $out/etc/ssl/certs
      ln -s /nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt $out/etc/ssl/certs

      cat $passwdContentsPath > $out/etc/passwd
      echo "" >> $out/etc/passwd

      cat $groupContentsPath > $out/etc/group
      echo "" >> $out/etc/group

      cat $shadowContentsPath > $out/etc/shadow
      echo "" >> $out/etc/shadow

      cat $endpointScriptPath > $out/etc/endpoint.sh
      ln -s /nix/var/nix/profiles $out/etc/profiles

      mkdir -p $out/usr
      ln -s /nix/var/nix/profiles/share $out/usr/

      mkdir -p $out/nix/var/nix/gcroots

      mkdir $out/tmp

      mkdir -p $out/var/tmp

      mkdir -p $out/etc/nix
      cat $nixConfContentsPath > $out/etc/nix/nix.conf

      mkdir -p $out/root
      mkdir -p $out/nix/var/nix/profiles/per-user/root

      ln -s ${profile} $out/nix/var/nix/profiles/default-1-link
      ln -s $out/nix/var/nix/profiles/default-1-link $out/nix/var/nix/profiles/default
      ln -s /nix/var/nix/profiles/default $out/root/.nix-profile

      ln -s ${channel} $out/nix/var/nix/profiles/per-user/root/channels-1-link
      ln -s $out/nix/var/nix/profiles/per-user/root/channels-1-link $out/nix/var/nix/profiles/per-user/root/channels

      mkdir -p $out/root/.nix-defexpr
      ln -s $out/nix/var/nix/profiles/per-user/root/channels $out/root/.nix-defexpr/channels
      ln -s ${nix-channels} $out/root/.nix-channels

      mkdir -p $out${userHome}
      mkdir -p $out${userHome}/.nix-defexpr
      ln -s $out/nix/var/nix/profiles/per-user/root/channels $out${userHome}/.nix-defexpr/channels
      ln -s ${nix-channels} $out${userHome}/.nix-channels

      mkdir -p $out/bin $out/usr/bin
      ln -s ${pkgs.coreutils}/bin/env $out/usr/bin/env
      ln -s ${pkgs.bashInteractive}/bin/bash $out/bin/sh

      # The ${username}-tmp will be renamed to ${username} in fakeRootCommands script.
      mkdir -p $out/nix/var/nix/profiles/per-user/${username}-tmp

      ln -s ${channel} $out/nix/var/nix/profiles/per-user/${username}-tmp/channels-1-link
      ln -s $out/nix/var/nix/profiles/per-user/${username}-tmp/channels-1-link $out/nix/var/nix/profiles/per-user/${username}-tmp/channels

      HOME_ACTIVATION=${nixos.config.home-manager.users.${username}.home.activationPackage}
      find ''${HOME_ACTIVATION}/home-path/\
        -maxdepth 1 -type d | while read dir; do
        ln -s $dir $out/nix/var/nix/profiles/per-user/${username}-tmp/$(basename $dir)
      done
      find ''${HOME_ACTIVATION}/home-path/\
        -maxdepth 1 -type l | while read slink; do
        ln -s $slink $out/nix/var/nix/profiles/per-user/${username}-tmp/$(basename $slink)
      done

    '' + (lib.optionalString (flake-registry-path != null) ''
      nixCacheDir="/root/.cache/nix"
      mkdir -p $out$nixCacheDir
      globalisNormalUserFlakeRegistryPath="$nixCacheDir/flake-registry.json"
      ln -s ${flake-registry-path} $out$globalFlakeRegistryPath
      mkdir -p $out/nix/var/nix/gcroots/auto
      rootName=$(${pkgs.nix}/bin/nix --extra-experimental-features nix-command hash file --type sha1 --base32 <(echo -n $globalFlakeRegistryPath))
      ln -s $globalFlakeRegistryPath $out/nix/var/nix/gcroots/auto/$rootName
    ''));

in
pkgs.dockerTools.buildLayeredImageWithNixDb {

  inherit name tag maxLayers;

  contents = [ baseSystem ];

  extraCommands = ''
    rm -rf nix-support
    ln -s /nix/var/nix/profiles nix/var/nix/gcroots/profiles
  '';

  fakeRootCommands = ''
    chmod 1777 tmp
    chmod 1777 var/tmp

    export USER=${username}
    export HOME=${userHome}

    if [ ${toString etcActivation} ]; then
      ${nixos.config.system.build.etcActivationCommands}
    # FIXME(hacky!) It's a symlink, so let's make it properly.
      mv /nix/var/nix/profiles/per-user/{${username},root}
    fi

    mv /nix/var/nix/profiles/per-user/{${username}-tmp,${username}}

    # copy config files
    ln -s ${./configuration.nix} /etc/configuration.nix
    ln -s ${podCommonPath} /etc/common

    # FIXME(hacky!) It should be possible to copy the configs without removing the directory!
    ln -s ${podNixosPath} /etc/nixos

    if [ ${toString homeActivation} ]; then
      HOME_ACTIVATION=${nixos.config.home-manager.users.${username}.home.activationPackage}
      mkdir -p /build
      $HOME_ACTIVATION/activate
      # FIXME(hacky!) It's pointing to the per-user/root directory, so this fix it.
      rm ${userHome}/.nix-profile
    fi

    ln -s /nix/var/nix/profiles/per-user/${username} ${userHome}/.nix-profile

    chown ${ userGroupIds} /nix/var/nix/profiles/per-user/${username}
    chown -R ${ userGroupIds} /nix/var/nix/profiles/per-user/${username}
    chown ${ userGroupIds} ${userHome}
    find ${userHome} -type d | while read dir; do
      if [[ $dir != "${mountingDir}" ]] && [[ $dir != ${mountingDir}/* ]]; then
        chown ${ userGroupIds} $dir
      fi
    done

  '';

  enableFakechroot = true;

  created = "now";
  config = {
    Cmd = [ "/nix/var/nix/profiles/default/bin/bash" ];
    Entrypoint = [ "/nix/var/nix/profiles/default/bin/bash" "/etc/endpoint.sh" ];
    WorkingDir = userHome;
    Volumes = { };
    Env = [
      "USER=root"
      "PATH=${lib.concatStringsSep ":" [
        "${userHome}/.nix-profile/bin"
        "/root/.nix-profile/bin"
        "/nix/var/nix/profiles/default/bin"
        "/nix/var/nix/profiles/default/sbin"
      ]}"
      "MANPATH=${lib.concatStringsSep ":" [
        "${userHome}/.nix-profile/share/man"
        "/root/.nix-profile/share/man"
        "/nix/var/nix/profiles/default/share/man"
      ]}"
      "SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
      "GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
      "NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
      "NIX_PATH=/nix/var/nix/profiles/per-user/${username}/channels:${userHome}/.nix-defexpr/channels"
      "TERM=screen-256color"
    ];
  };

}

