Hello! I continue my journey to expore Nix as a (Ruby) development environment. Now I have a problem using nix-shell
for a Ruby app which depends on a gem from a private Git repository. shell.nix
is trivial: it uses mkShell
with ruby
and gems built by bundlerEnv
as buildInputs
.
When I launch nix-shell
, it fails because it cannot build a derivation for the private gem. Related excerpt from the output:
# ... snip ...
exporting https://gitlab.com/<my-org>/<my-gem>.git (rev <revision>) into /nix/store/3d57xxnxnfih316is8w0gynir4ir10d4-<my-gem>-7727f7a
Initialized empty Git repository in /nix/store/3d57xxnxnfih316is8w0gynir4ir10d4-<my-gem>-7727f7a/.git/
fatal: could not read Username for 'https://gitlab.com': Device not configured
fatal: could not read Username for 'https://gitlab.com': Device not configured
Unable to checkout <revision> from https://gitlab.com/<my-org>/<my-gem>.git
# ... snip ...
cannot build derivation '/nix/store/x3mvcly1n3klawih9n2a9g7qcs6zsfz7-<my-app-gems>.drv': 1 dependencies couldn't be built
error: build of '/nix/store/x3mvcly1n3klawih9n2a9g7qcs6zsfz7-<my-app-gems>.drv' failed
This discussion mentions private Git repos but it doesn’t lead to a conclusion on this topic. bundix --magic
kinda works (it asks for the credentials) but it has some drawbacks:
- it doesn’t install gems’ executables so I have to use e.g.
bundle exec rails
instead of rails
;
- it doesn’t seem to use
pkgs.defaultGemConfig
so I have to manually install native dependencies (e.g. libxml2
, libiconv
, libsodium
, etc.), otherwise bundix --magic
will fail;
- it modifies local
.bundle/config
.
Is there any solution? cc @manveru, @zimbatm.
An unrelated question: my app depends on two gems, both of them install bin/console
which makes nix-shell
fail because of collisions. I have solved it by overriding meta.priority
for one of them in bundlerEnv
's gemConfig
. Is this the right way?
The funny thing is that I have managed to build this derivation somehow, I just don’t remember how. Then I ran nix-collect-garbage
and now I cannot reproduce it.
A possible workaround would be setting the BUNDLE_GITLAB__COM
environment variable (as described in the bundle config
docs). It works if I set this variable in a nix shell and manually run bundle install
:
{ pkgs ? import <nixpkgs> {}
}:
pkgs.mkShell {
buildInputs = with pkgs; [
ruby
libxml2
libsodium
libiconv
];
BUNDLE_GITLAB__COM = "user:pass";
}
I expected it to work with bundlerEnv
and gem config overrides:
{ pkgs ? import <nixpkgs> {}
}:
let
ruby = pkgs.ruby_2_5;
gems = pkgs.bundlerEnv {
name = "my-app-gems";
groups = [ "default" ] ++ environments;
gemdir = ./.;
gemConfig = pkgs.defaultGemConfig // {
my-gem = attrs: {
BUNDLE_GITLAB__COM = "user:pass";
};
};
inherit ruby;
};
environments = ["development" "test" ];
in pkgs.mkShell {
buildInputs = with pkgs; [
ruby
gems
bundix
];
}
Unfortunately, it fails with the same error message.
I managed to make it work by overriding the gem’s source URL in gem config!
# ... snip ...
gemConfig = pkgs.defaultGemConfig // {
my-gem = { source, ...}: {
source = source // {
url = "https://<user>:<pass>@gitlab.com/<my-org>/<my-gem>.git";
};
};
};
# ... snip ...
Since I didn’t want to store my Gitlab credentials in version control, I’ve come up with a solution where credentials are read from a git-ignored file:
{ pkgs ? import <nixpkgs> {}
}:
let
ruby = pkgs.ruby_2_5;
add-credentials-to-source = credentials-file:
let
inherit (pkgs.lib) removeSuffix pathExists;
inherit (builtins) trace readFile toString replaceStrings;
error-msg = "expected ${toString credentials-file} to exist";
credentials =
assert !pathExists credentials-file -> trace error-msg false;
removeSuffix "\n" (readFile credentials-file);
in
host: { url, ...}@source: source // {
url = replaceStrings ["https://${host}"] ["https://${credentials}@${host}"] url;
};
add-gitlab-credentials = add-credentials-to-source ./.gitlab-credentials "gitlab.com";
gems = pkgs.bundlerEnv {
name = "my-app-gems";
groups = [ "default" ] ++ environments;
gemdir = ./.;
gemConfig = pkgs.defaultGemConfig // {
my-gem = { source, ... }: {
source = add-gitlab-credentials source;
};
};
inherit ruby;
};
environments = ["development" "test" ];
in pkgs.mkShell {
buildInputs = with pkgs; [
ruby
gems
bundix
];
}
It’s not pretty, but it’s working. I’m marking this post as the solution until someone provides a better one.
I just spent some hours trying to figure how to get fetchgit use the token set in git config - until i found your solution - thanks for posting this!
Just for people trying to do the same thing, i used builtins.getEnv
instead of readFile
, since everyone in our team already uses direnv to setup their local environment.
So my final setup looks like this:
let
gem_credentials = builtins.getEnv "GITHUB_ACCESS_TOKEN";
in
gems = pkgs.bundlerEnv rec {
ruby = pkgs.ruby_3_2;
gemfile = ./Gemfile;
lockfile = ./Gemfile.lock;
gemset = ./gemset.nix;
groups = [ "default" "development" "test" "production" ];
gemConfig = pkgs.defaultGemConfig // {
GEM_NAME = attrs: {
source = attrs.source // {
url = builtins.replaceStrings [ "https://github.com" ] [ "https://${gem_credentials}@github.com" ] attrs.source.url;
};
};
};
};