Help with https flake inputs

Hi all. I’m a bit of a noob, hoping for some help.

I’m maintaining a flake.nix for a project. So far, there has been a nixpkgs entry for everything that I need, so I’ve been merrily populating my devshell with packages and hopping back and forth between nixos and macos + nix with abandon.

Today I tried to add chronoctl (a cli utility for chronosphere) in the devshell for my project, but I was stopped by the fact that it is not in nixpkgs, so now I need to figure out how to hack it myself.

Here’s a simplified version of my flake.nix

{
  inputs = {
    flake-utils = {
      url = "github:numtide/flake-utils";
    };
    chronoctl = {
      type = "file";
      url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-linux-amd64";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, flake-utils, }:
  flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      packages = {
        default = self.packages.${system}.hello;
      };

      devShells.default = pkgs.mkShell {
        packages = [
          # chronoctl... something ???
        ];
      };
    });
}

This is certainly wrong. Primarily because it fails with an error:

error: 'outputs' at /nix/store/xpps3iqjshxqjp2m5z4l9h0mxv8i8drs-source/flake.nix:16:13 called with unexpected argument 'chronoctl'

       at «string»:45:21:

           44|
           45|           outputs = flake.outputs (inputs // { self = result; });
             |                     ^
           46|

I have also tried it like this:

    chronoctl = {
      url = "file+https://storage.googleapis.com/chronosphere-release/latest/chronoctl-linux-amd64";
      flake = false;
    };

…but then I get a different error:

error: file:// URL 'file+https://storage.googleapis.com/chronosphere-release/latest/chronoctl-linux-amd64' has unexpected authority 'storage.googleapis.com'

Input reference trouble aside, prehaps the bigger conceptual problem is that users on darwin, or users on ARM, will have the linux-amd64 version of chronoctl as an input, which won’t work for them.

Presumably I need to write something that maps the $system parameter of eachDefaultSystem to the appropriate download URL (and instructs nix to chmod +x it).

One way to do this would be to add a package for chronoctl to nixpkgs, but I kind of want to hack it to a working state locally before I publish anything. How can I do that?

I thought of having a folder in my repo which is a separate flake that handles the download, chmod, and makes a package. I would then reference that directory as an input from the flake at the root of the repo. But I’ve never seen this done so I’m reluctant to walk that path without at least a :+1: from somebody who knows nix better than I do.

I figured out something that sort-of works. I can just not have it be an input at all and build the package right there in the outputs section, like so:

{
  inputs = {
    flake-utils = {
      url = "github:numtide/flake-utils";
    };
  };

  outputs = { self, nixpkgs, flake-utils, }:
  flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
      chronoctl = rec {
        archMap = {
          "x86_64-linux" = {
            suffix = "linux-amd64";
            hash = "sha256-bVIdvOeHLU7/ZrGcdQW/5bov2Nh57my+9065tq7YZcU=";
          };
          "x86_64-darwin" = {
            suffix = "darwin-amd64";
            hash ="0000000000000000000000000000000000000000000000000000";
          };
          "aarch64-linux" = {
            suffix = "linux-arm64";
            hash ="0000000000000000000000000000000000000000000000000000";
          };
          "aarch64-darwin" = {
            suffix = "darwin-arm64";
            hash ="0000000000000000000000000000000000000000000000000000";
          };
        };

        arch = archMap.${system} or (throw "Unsupported system: ${system}");
        url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-${arch.suffix}";
        sha256 = arch.hash;
      };
    in
    rec {
      packages = {
        chronoctl = pkgs.stdenv.mkDerivation {
          pname = "chronoctl";
          version = "latest";
          src = pkgs.fetchurl {
            url = chronoctl.url;
            sha256 = chronoctl.sha256;
          };

          phases = "installPhase";

          installPhase = ''
            install -D $src $out/bin/chronoctl
          '';
        };
      };

      devShells.default = pkgs.mkShell {
        packages = [
          packages.chronoctl
        ];
      };
    });
}

It still feels a bit like I’m doing by hand what nix flake lock does automatically. If there’s a way to move the url-generation logic into the inputs section so that it would at least handle the hashes via flake.lock, I’d like to know it.

The original issue was because you weren’t including either a chronoctl parameter in your outputs function or a ... to indicate that it could take additional parameters besides the ones you specified.

2 Likes

This works for me. Note that flakes don’t allow nested input attributes, so it’s not possible to have all of the systems defined under one chronosphere attribute.

{
  inputs = {
    flake-utils = {
      url = "github:numtide/flake-utils";
    };
    chronosphere-aarch64-darwin = {
      type = "file";
      url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-darwin-arm64";
      flake = false;
    };
    chronosphere-x86_64-darwin = {
      type = "file";
      url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-darwin-amd64";
      flake = false;
    };
    chronosphere-aarch64-linux = {
      type = "file";
      url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-linux-arm64";
      flake = false;
    };
    chronosphere-x86_64-linux = {
      type = "file";
      url = "https://storage.googleapis.com/chronosphere-release/latest/chronoctl-linux-amd64";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, flake-utils, ... }:
  flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      packages.chronoctl = pkgs.stdenv.mkDerivation {
        pname = "chronoctl";
        version = "latest";
        src = self.inputs."chronosphere-${system}";

        phases = "installPhase";

        installPhase = ''
          install -D $src $out/bin/chronoctl
        '';
      };

      devShells.default = pkgs.mkShell {
        packages = [
          self.packages.${system}.chronoctl
        ];
      };
    });
}